diff --git a/frontend/src/i18n/locales/en/admin/settings.ts b/frontend/src/i18n/locales/en/admin/settings.ts index 5e54b22214..305c33841e 100644 --- a/frontend/src/i18n/locales/en/admin/settings.ts +++ b/frontend/src/i18n/locales/en/admin/settings.ts @@ -144,6 +144,7 @@ export default { passkeyRPID: 'RP ID', passkeyOrigins: 'Allowed HTTPS origins', passkeyValueNotConfigured: 'Not configured', + passkeyDeploymentHint: 'Ask the server operator to set webauthn.enabled to true, configure webauthn.rp_id (domain only) and webauthn.rp_origins (full HTTPS origins), then restart the service.', stepUp: 'Step-up 2FA for Sensitive Operations', stepUpHint: 'When enabled, sensitive operations (account/proxy export, backup creation and download, S3 config changes, promoting admins) require a recent TOTP verification (valid for 15 minutes). Your own account must have 2FA enabled before turning this on; turning it off also requires step-up verification.', stepUpEnableRequiresTotp: 'Enable 2FA (TOTP) for your own account in Profile before turning on step-up verification.', diff --git a/frontend/src/i18n/locales/zh/admin/settings.ts b/frontend/src/i18n/locales/zh/admin/settings.ts index 4989305c4d..ae625b8766 100644 --- a/frontend/src/i18n/locales/zh/admin/settings.ts +++ b/frontend/src/i18n/locales/zh/admin/settings.ts @@ -144,6 +144,7 @@ export default { passkeyRPID: 'RP ID', passkeyOrigins: '允许的 HTTPS 来源', passkeyValueNotConfigured: '未配置', + passkeyDeploymentHint: '请由服务器运维在部署配置中将 webauthn.enabled 设为 true,填写 webauthn.rp_id(仅域名)与 webauthn.rp_origins(完整 HTTPS 来源),然后重启服务。', stepUp: '敏感操作二次验证 (step-up 2FA)', stepUpHint: '开启后,账号/代理导出、备份创建与下载、S3 配置修改、提升管理员等敏感操作需要先完成 TOTP 二次验证(15 分钟内有效)。开启前需本人已启用 2FA;关闭该开关本身也需要二次验证。', stepUpEnableRequiresTotp: '开启敏感操作二次验证前,请先在个人资料中为当前账号启用 2FA (TOTP)。', diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index e3047e684c..3a8baf4bd8 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -1631,6 +1631,9 @@ ) }}
++ {{ t("admin.settings.security.passkeyDeploymentHint") }} +
diff --git a/frontend/src/views/admin/__tests__/SettingsView.spec.ts b/frontend/src/views/admin/__tests__/SettingsView.spec.ts index 3222314683..36d505b3da 100644 --- a/frontend/src/views/admin/__tests__/SettingsView.spec.ts +++ b/frontend/src/views/admin/__tests__/SettingsView.spec.ts @@ -225,6 +225,8 @@ vi.mock("vue-i18n", async () => { "admin.settings.upstreamBillingProbe.intervalHint": "范围 5–1440 分钟。", "admin.settings.upstreamBillingProbe.saved": "上游倍率自动探测设置已保存", "admin.settings.upstreamBillingProbe.saveFailed": "保存上游倍率自动探测设置失败", + "admin.settings.security.passkeyDeploymentHint": + "请由服务器运维在部署配置中将 webauthn.enabled 设为 true,填写 webauthn.rp_id(仅域名)与 webauthn.rp_origins(完整 HTTPS 来源),然后重启服务。", "admin.settings.site.uploadImage": "上传图片", "admin.settings.site.remove": "移除", "admin.settings.platformQuota.platform": "平台", @@ -737,6 +739,7 @@ describe("admin SettingsView payment visible method controls", () => { expect(toggle.attributes("disabled")).toBeUndefined(); expect(settings.text()).toContain("sub3.nebula-spaces.com"); expect(settings.text()).toContain("https://sub3.nebula-spaces.com"); + expect(settings.text()).not.toContain("webauthn.enabled"); await toggle.setValue(false); await wrapper.find("form").trigger("submit.prevent"); @@ -762,9 +765,14 @@ describe("admin SettingsView payment visible method controls", () => { const settings = wrapper.get('[data-testid="passkey-settings"]'); expect(settings.get('[data-testid="passkey-toggle"]').attributes("disabled")).toBeDefined(); - expect(settings.get('[data-testid="passkey-config-status"]').text()).toContain( + const status = settings.get('[data-testid="passkey-config-status"]'); + expect(status.text()).toContain( "admin.settings.security.passkeyNotConfigured", ); + expect(status.text()).toContain("webauthn.enabled"); + expect(status.text()).toContain("webauthn.rp_id"); + expect(status.text()).toContain("webauthn.rp_origins"); + expect(status.text()).toContain("然后重启服务"); }); it("loads, edits, validates, and saves forwarded client-IP headers", async () => {