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