From a0056ff15b241af2812aef2767426d4c818ec127 Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Tue, 21 Jul 2026 15:15:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=A8=E7=8E=AF=E5=A2=83=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E5=A2=9E=E5=8A=A0=E8=B0=B7=E6=AD=8C=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E5=99=A8=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-example | 4 ++++ app/admin/controller/Index.php | 23 ++++++++++++++++++++- app/admin/controller/auth/Admin.php | 4 ++++ app/admin/lang/en.php | 1 + app/admin/lang/zh-cn.php | 1 + app/common/library/AdminTotp.php | 5 +++++ config/buildadmin.php | 2 ++ web/src/stores/interface/index.ts | 2 ++ web/src/stores/siteConfig.ts | 1 + web/src/views/backend/auth/admin/index.vue | 6 ++++++ web/src/views/backend/routine/adminInfo.vue | 4 +++- 11 files changed, 51 insertions(+), 2 deletions(-) diff --git a/.env-example b/.env-example index 99eddb2..b0e8944 100644 --- a/.env-example +++ b/.env-example @@ -7,6 +7,10 @@ APP_DEFAULT_TIMEZONE = Asia/Shanghai # 语言 LANG_DEFAULT_LANG = zh-cn + +# 管理员谷歌验证器(TOTP):1=开启(登录需验证/绑定),0=关闭 +ADMIN_TOTP_ENABLE=1 + # Database DATABASE_TYPE = mysql DATABASE_HOSTNAME = 127.0.0.1 diff --git a/app/admin/controller/Index.php b/app/admin/controller/Index.php index 703bd6e..496028d 100644 --- a/app/admin/controller/Index.php +++ b/app/admin/controller/Index.php @@ -51,6 +51,7 @@ class Index extends Backend '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(), ], ]); } @@ -109,6 +110,13 @@ class Index extends Backend 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'), [ @@ -127,7 +135,8 @@ class Index extends Backend } return $this->success('', [ - 'captcha' => $captchaSwitch + 'captcha' => $captchaSwitch, + 'totpEnable' => AdminTotp::isEnabled(), ]); } @@ -136,6 +145,10 @@ class Index extends Backend $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]); } @@ -169,6 +182,10 @@ class Index extends Backend $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]); } @@ -223,6 +240,10 @@ class Index extends Backend $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]); } diff --git a/app/admin/controller/auth/Admin.php b/app/admin/controller/auth/Admin.php index cd756d4..1d053c8 100644 --- a/app/admin/controller/auth/Admin.php +++ b/app/admin/controller/auth/Admin.php @@ -231,6 +231,10 @@ class Admin extends Backend $response = $this->initializeBackend($request); if ($response !== null) return $response; + if (!\app\common\library\AdminTotp::isEnabled()) { + return $this->error(__('Google Authenticator is disabled')); + } + if ($request->method() !== 'POST') { return $this->error(__('Method not allowed'), [], 0, ['statusCode' => 405]); } diff --git a/app/admin/lang/en.php b/app/admin/lang/en.php index 117468d..db7cab1 100644 --- a/app/admin/lang/en.php +++ b/app/admin/lang/en.php @@ -120,6 +120,7 @@ return [ 'Google Authenticator not bound' => 'Google Authenticator not bound', 'Reset Google Authenticator' => 'Reset Google Authenticator', 'Google Authenticator reset successfully' => 'Google Authenticator reset. The admin must re-bind on next login', + 'Google Authenticator is disabled' => 'Google Authenticator is disabled', 'Cannot reset your own authenticator, please modify in database' => 'Cannot reset your own authenticator. Super admin recovery requires database change', 'Login failed' => 'Login failed', ]; \ No newline at end of file diff --git a/app/admin/lang/zh-cn.php b/app/admin/lang/zh-cn.php index 9b99ae9..5f8de9f 100644 --- a/app/admin/lang/zh-cn.php +++ b/app/admin/lang/zh-cn.php @@ -139,6 +139,7 @@ return [ 'Google Authenticator not bound' => '谷歌验证器未绑定', 'Reset Google Authenticator' => '重置谷歌验证器', 'Google Authenticator reset successfully' => '谷歌验证器已重置,该管理员下次登录需重新绑定', + 'Google Authenticator is disabled' => '谷歌验证器功能已关闭', 'Cannot reset your own authenticator, please modify in database' => '不能重置自己的验证器,超管丢失验证器请在数据库中修改', 'Login failed' => '登录失败', ]; \ No newline at end of file diff --git a/app/common/library/AdminTotp.php b/app/common/library/AdminTotp.php index bddd206..df569b4 100644 --- a/app/common/library/AdminTotp.php +++ b/app/common/library/AdminTotp.php @@ -20,6 +20,11 @@ class AdminTotp private static ?TwoFactorAuth $tfa = null; + public static function isEnabled(): bool + { + return (bool) config('buildadmin.admin_totp_enable', true); + } + private static function tfa(): TwoFactorAuth { if (self::$tfa === null) { diff --git a/config/buildadmin.php b/config/buildadmin.php index dbe60cf..546d42e 100644 --- a/config/buildadmin.php +++ b/config/buildadmin.php @@ -10,6 +10,8 @@ return [ 'user_login_captcha' => false, // 是否开启管理员登录验证码 'admin_login_captcha' => false, + // 是否开启管理员谷歌验证器(TOTP),由 .env ADMIN_TOTP_ENABLE 控制 + 'admin_totp_enable' => filter_var(env('ADMIN_TOTP_ENABLE', '1'), FILTER_VALIDATE_BOOLEAN), // 会员登录失败可重试次数,false则无限 'user_login_retry' => 10, // 管理员登录失败可重试次数,false则无限 diff --git a/web/src/stores/interface/index.ts b/web/src/stores/interface/index.ts index 61ffcd5..91aaa7d 100644 --- a/web/src/stores/interface/index.ts +++ b/web/src/stores/interface/index.ts @@ -154,6 +154,8 @@ export interface SiteConfig { recordNumber?: string // 内容分发网络URL的参数,格式如 imageMogr2/format/heif cdnUrlParams: string + // 是否开启管理员谷歌验证器 + totpEnable?: boolean // 初始化状态 initialize: boolean diff --git a/web/src/stores/siteConfig.ts b/web/src/stores/siteConfig.ts index 6829675..b9d4a49 100644 --- a/web/src/stores/siteConfig.ts +++ b/web/src/stores/siteConfig.ts @@ -15,6 +15,7 @@ export const useSiteConfig = defineStore('siteConfig', { headNav: [], recordNumber: '', cdnUrlParams: '', + totpEnable: true, initialize: false, userInitialize: false, } diff --git a/web/src/views/backend/auth/admin/index.vue b/web/src/views/backend/auth/admin/index.vue index bc52cde..73e7226 100644 --- a/web/src/views/backend/auth/admin/index.vue +++ b/web/src/views/backend/auth/admin/index.vue @@ -26,6 +26,7 @@ import { defaultOptButtons } from '/@/components/table' import { baTableApi } from '/@/api/common' import { resetTotp } from '/@/api/backend/auth/admin' import { useAdminInfo } from '/@/stores/adminInfo' +import { useSiteConfig } from '/@/stores/siteConfig' import { useI18n } from 'vue-i18n' defineOptions({ @@ -34,9 +35,13 @@ defineOptions({ const { t } = useI18n() const adminInfo = useAdminInfo() +const siteConfig = useSiteConfig() /** 超管或拥有 resetTotp 权限,且不能操作自己 */ const canManageTotp = (row: TableRow) => { + if (!siteConfig.totpEnable) { + return false + } if (row.id == adminInfo.id) { return false } @@ -114,6 +119,7 @@ const baTable = new baTableClass( replaceValue: { true: t('auth.admin.Bound'), false: t('auth.admin.Not bound') }, operator: false, width: 120, + show: siteConfig.totpEnable, }, { label: t('auth.admin.Last login'), diff --git a/web/src/views/backend/routine/adminInfo.vue b/web/src/views/backend/routine/adminInfo.vue index 3ea0715..6f3e87d 100644 --- a/web/src/views/backend/routine/adminInfo.vue +++ b/web/src/views/backend/routine/adminInfo.vue @@ -38,7 +38,7 @@ - + {{ state.adminInfo.totp_bound ? t('routine.adminInfo.Bound') : t('routine.adminInfo.Not bound') }} @@ -117,6 +117,7 @@ import { uuid } from '../../../utils/random' import { buildValidatorData } from '/@/utils/validate' import { fileUpload } from '/@/api/common' import { useAdminInfo } from '/@/stores/adminInfo' +import { useSiteConfig } from '/@/stores/siteConfig' import { isEmpty } from 'lodash-es' defineOptions({ @@ -127,6 +128,7 @@ const { t } = useI18n() const formRef = useTemplateRef('formRef') const adminInfoStore = useAdminInfo() +const siteConfig = useSiteConfig() const state: { adminInfo: anyObj