initializeBackend($request); if ($response !== null) return $response; $adminInfo = $this->auth->getInfo(); $adminInfo['super'] = $this->auth->isSuperAdmin(); unset($adminInfo['token'], $adminInfo['refresh_token']); $menus = $this->auth->getMenus(); if (!$menus) { return $this->error(__('No background menu, please contact super administrator!')); } $apiUrl = config('buildadmin.api_url'); if (!$apiUrl || $apiUrl === 'https://api.buildadmin.com') { $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $apiUrl = $scheme . '://' . $request->host(); } return $this->success('', [ 'adminInfo' => $adminInfo, 'menus' => $menus, 'siteConfig' => [ 'siteName' => get_sys_config('site_name'), 'version' => get_sys_config('version'), 'apiUrl' => $apiUrl, 'upload' => keys_to_camel_case(get_upload_config($request), ['max_size', 'save_name', 'allowed_suffixes', 'allowed_mime_types', 'forbidden_suffixes']), 'cdnUrl' => full_url(), 'cdnUrlParams' => config('buildadmin.cdn_url_params'), ], ]); } public function login(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) return $response; if ($this->auth->isLogin()) { return $this->success(__('You have already logged in. There is no need to log in again~'), [ 'type' => $this->auth::LOGGED_IN ], $this->auth::LOGIN_RESPONSE_CODE); } $captchaSwitch = config('buildadmin.admin_login_captcha'); if ($request->method() === 'POST') { $username = $request->post('username'); $password = $request->post('password'); $keep = $request->post('keep'); $rules = [ 'username' => 'required|string|min:3|max:30', 'password' => 'required|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/', ]; $data = ['username' => $username, 'password' => $password]; if ($captchaSwitch) { $rules['captchaId'] = 'required|string'; $rules['captchaInfo'] = 'required|string'; $data['captchaId'] = $request->post('captchaId'); $data['captchaInfo'] = $request->post('captchaInfo'); } try { Validator::make($data, $rules, [ 'username.required' => __('Username'), 'password.required' => __('Password'), 'password.regex' => __('Please input correct password'), ])->validate(); } catch (ValidationException $e) { return $this->error($e->getMessage()); } if ($captchaSwitch) { $captchaObj = new ClickCaptcha(); if (!$captchaObj->check($data['captchaId'], $data['captchaInfo'])) { return $this->error(__('Captcha error')); } } AdminLog::instance($request)->setTitle(__('Login')); if (!$this->auth->verifyCredentials($username, $password)) { $msg = $this->auth->getError(); return $this->error($msg ?: __('Incorrect user name or password!')); } if ($this->auth->hasTotpBound()) { $tempToken = AdminTotp::createPendingToken($this->auth->id, AdminTotp::TOKEN_TYPE_VERIFY); return $this->success(__('Please enter Google Authenticator code'), [ 'type' => $this->auth::NEED_TOTP, 'tempToken' => $tempToken, 'username' => $this->auth->username, ]); } $tempToken = AdminTotp::createPendingToken($this->auth->id, AdminTotp::TOKEN_TYPE_BIND); return $this->success(__('Please bind Google Authenticator'), [ 'type' => $this->auth::NEED_BIND_TOTP, 'tempToken' => $tempToken, 'username' => $this->auth->username, ]); } return $this->success('', [ 'captcha' => $captchaSwitch ]); } public function totpBindInit(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) return $response; if ($request->method() !== 'POST') { return $this->error(__('Method not allowed'), [], 0, ['statusCode' => 405]); } $tempToken = (string) $request->post('tempToken', ''); $adminId = AdminTotp::resolvePendingToken($tempToken, AdminTotp::TOKEN_TYPE_BIND); if ($adminId <= 0) { return $this->error(__('TOTP session expired, please login again')); } if (!$this->auth->loadAdminById($adminId)) { return $this->error($this->auth->getError()); } if ($this->auth->hasTotpBound()) { return $this->error(__('Google Authenticator already bound')); } $secret = AdminTotp::generateSecret(); $label = $this->auth->username; $qrCode = AdminTotp::getQrDataUri($label, $secret); return $this->success('', [ 'secret' => $secret, 'qrCode' => $qrCode, 'username' => $label, ]); } public function totpBindConfirm(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) return $response; if ($request->method() !== 'POST') { return $this->error(__('Method not allowed'), [], 0, ['statusCode' => 405]); } $tempToken = (string) $request->post('tempToken', ''); $secret = (string) $request->post('secret', ''); $code = (string) $request->post('code', ''); $keep = (bool) $request->post('keep'); if ($tempToken === '' || $secret === '' || $code === '') { return $this->error(__('Parameter %s can not be empty', [''])); } $adminId = AdminTotp::resolvePendingToken($tempToken, AdminTotp::TOKEN_TYPE_BIND); if ($adminId <= 0) { return $this->error(__('TOTP session expired, please login again')); } if (!AdminTotp::verifyCode($secret, $code)) { return $this->error(__('Google Authenticator code error')); } if (!$this->auth->loadAdminById($adminId)) { return $this->error($this->auth->getError()); } if ($this->auth->hasTotpBound()) { AdminTotp::deletePendingToken($tempToken); return $this->error(__('Google Authenticator already bound')); } $encrypted = AdminTotp::encryptSecret($secret); if ($encrypted === '') { return $this->error(__('Google Authenticator bind failed')); } Admin::where('id', $adminId)->update([ 'totp_secret' => $encrypted, 'totp_bind_time' => time(), ]); AdminTotp::deletePendingToken($tempToken); if (!$this->auth->finalizeLogin($keep)) { return $this->error($this->auth->getError() ?: __('Google Authenticator bind failed')); } return $this->buildLoginSuccessResponse($keep, __('Google Authenticator bound successfully')); } public function totpVerify(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) return $response; if ($request->method() !== 'POST') { return $this->error(__('Method not allowed'), [], 0, ['statusCode' => 405]); } $tempToken = (string) $request->post('tempToken', ''); $code = (string) $request->post('code', ''); $keep = (bool) $request->post('keep'); if ($tempToken === '' || $code === '') { return $this->error(__('Parameter %s can not be empty', [''])); } $adminId = AdminTotp::resolvePendingToken($tempToken, AdminTotp::TOKEN_TYPE_VERIFY); if ($adminId <= 0) { return $this->error(__('TOTP session expired, please login again')); } if (!$this->auth->loadAdminById($adminId)) { return $this->error($this->auth->getError()); } if (!$this->auth->hasTotpBound()) { AdminTotp::deletePendingToken($tempToken); return $this->error(__('Google Authenticator not bound')); } $encrypted = $this->auth->getAdmin()->getData('totp_secret'); if (!AdminTotp::verifyStoredCode($encrypted, $code)) { $this->auth->loginFailed(); return $this->error(__('Google Authenticator code error')); } AdminTotp::deletePendingToken($tempToken); if (!$this->auth->finalizeLogin($keep)) { return $this->error($this->auth->getError() ?: __('Login failed')); } return $this->buildLoginSuccessResponse($keep); } public function logout(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) return $response; if ($request->method() === 'POST') { $refreshToken = $request->post('refreshToken', ''); if ($refreshToken) { Token::delete((string) $refreshToken); } $this->auth->logout(); return $this->success(); } return $this->error(__('Method not allowed'), [], 0, ['statusCode' => 405]); } private function buildLoginSuccessResponse(bool $keep, ?string $message = null): Response { $userInfo = $this->auth->getInfo(); $adminId = $this->auth->id; $keepTime = (int) config('buildadmin.admin_token_keep_time', 86400 * 3); if (empty($userInfo['token']) && $adminId) { $userInfo['token'] = Random::uuid(); Token::set($userInfo['token'], \app\admin\library\Auth::TOKEN_TYPE, $adminId, $keepTime); } if (empty($userInfo['refresh_token']) && $keep && $adminId) { $userInfo['refresh_token'] = Random::uuid(); Token::set($userInfo['refresh_token'], \app\admin\library\Auth::TOKEN_TYPE . '-refresh', $adminId, 2592000); } return $this->success($message ?: __('Login succeeded!'), [ 'userInfo' => $userInfo, ]); } }