321 lines
12 KiB
PHP
321 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\admin\controller;
|
|
|
|
use ba\ClickCaptcha;
|
|
use ba\Random;
|
|
use app\admin\model\Admin;
|
|
use app\common\facade\Token;
|
|
use app\admin\model\AdminLog;
|
|
use app\common\library\AdminTotp;
|
|
use app\common\controller\Backend;
|
|
use support\validation\Validator;
|
|
use support\validation\ValidationException;
|
|
use Webman\Http\Request;
|
|
use support\Response;
|
|
|
|
class Index extends Backend
|
|
{
|
|
protected array $noNeedLogin = ['logout', 'login', 'totpVerify', 'totpBindInit', 'totpBindConfirm'];
|
|
protected array $noNeedPermission = ['index'];
|
|
|
|
public function index(Request $request): Response
|
|
{
|
|
$response = $this->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'),
|
|
'totpEnable' => AdminTotp::isEnabled(),
|
|
],
|
|
]);
|
|
}
|
|
|
|
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 (!AdminTotp::isEnabled()) {
|
|
if (!$this->auth->finalizeLogin((bool) $keep)) {
|
|
return $this->error($this->auth->getError() ?: __('Login failed'));
|
|
}
|
|
return $this->buildLoginSuccessResponse((bool) $keep);
|
|
}
|
|
|
|
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,
|
|
'totpEnable' => AdminTotp::isEnabled(),
|
|
]);
|
|
}
|
|
|
|
public function totpBindInit(Request $request): Response
|
|
{
|
|
$response = $this->initializeBackend($request);
|
|
if ($response !== null) return $response;
|
|
|
|
if (!AdminTotp::isEnabled()) {
|
|
return $this->error(__('Google Authenticator is disabled'));
|
|
}
|
|
|
|
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 (!AdminTotp::isEnabled()) {
|
|
return $this->error(__('Google Authenticator is disabled'));
|
|
}
|
|
|
|
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 (!AdminTotp::isEnabled()) {
|
|
return $this->error(__('Google Authenticator is disabled'));
|
|
}
|
|
|
|
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,
|
|
]);
|
|
}
|
|
}
|