Skip to content

fix(web): respect passkey availability in secure verification - #6155

Open
Imzl-zl wants to merge 2 commits into
QuantumNous:mainfrom
Imzl-zl:fix/passkey-availability
Open

fix(web): respect passkey availability in secure verification#6155
Imzl-zl wants to merge 2 commits into
QuantumNous:mainfrom
Imzl-zl:fix/passkey-availability

Conversation

@Imzl-zl

@Imzl-zl Imzl-zl commented Jul 13, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • 本描述已根据代码逻辑人工整理。
  • 本次修改由 AI 辅助分析与实现,提交者已审查代码逻辑并完成相关验证。

📝 变更描述 / Description

修复默认前端在 Passkey 不可用时仍将其选为首选安全验证方式,以及系统全局关闭 Passkey 后个人资料仍允许启动注册的问题。

  • 用户 Passkey 状态接口新增 system_enabled,明确区分个人凭证绑定状态与系统全局可用状态。
  • 安全验证统一校验首选方式是否实际可用;Passkey 未绑定、系统关闭或当前设备不支持时自动回退到 2FA。
  • 验证对话框不再保留不可用的活动方式,避免提交隐藏或无效的 Passkey 验证。
  • 系统关闭 Passkey 时,个人资料页不再显示注册按钮,并向超级管理员提供 Passkey 设置入口。
  • 补充后端状态契约测试、验证方式选择测试及全部前端语言文案。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix)
  • ✨ 新功能 (New feature)
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有 Issues 与 PRs,确认不是重复提交。
  • Bug fix 说明: 此 PR 关联了可复现的 Issue [Bug] Passkey 不可用时安全验证仍首选 Passkey,且个人资料仍允许注册 #6152
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过相关测试、类型检查和生产构建。
  • 安全合规: 代码中无敏感凭据。

📸 运行证明 / Proof of Work

  • bun test src/features/auth/secure-verification/lib/select-verification-method.test.ts:4 项通过。
  • go test ./controller -run TestPasskeyStatusSeparatesCredentialAndSystemState -count=1:通过。
  • bun run typecheck:通过。
  • 变更文件定向 oxlintoxfmt --check:通过。
  • bun run i18n:sync:en、zh、zh-TW、fr、ja、ru、vi 均为 0 missing / 0 extras / 0 untranslated。
  • bun run build:生产构建通过。
  • git diff --check:通过。

说明:全仓 lintformat:checkcopyright:check 当前仍会命中 main 分支中与本 PR 无关的既有问题;本 PR 涉及文件的定向检查均已通过。

Summary by CodeRabbit

  • New Features
    • Passkey status now reports both user enrollment and system-wide availability separately.
    • Passkey management reflects admin/system disablement, including clearer badges and blocking registration with a toast.
    • Super admins can open Passkey settings directly from the admin-disabled warning.
    • Secure verification now picks a supported method with automatic fallback.
  • Bug Fixes
    • Verification options are no longer offered when passkeys are disabled by the system.
  • Localization
    • Added/updated Passkey availability and admin-disabled messaging across supported languages.
  • Tests
    • Added coverage for separating credential state from system state.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d9469167-b3e4-4ad1-b48f-a7265e85c11a

📥 Commits

Reviewing files that changed from the base of the PR and between af36501 and 31e4833.

📒 Files selected for processing (3)
  • web/default/src/features/auth/passkey/hooks/use-passkey-management.ts
  • web/default/src/features/auth/secure-verification/hooks/use-secure-verification.ts
  • web/default/src/features/auth/secure-verification/lib/select-verification-method.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/default/src/features/auth/passkey/hooks/use-passkey-management.ts

Walkthrough

The Passkey status API now distinguishes credential binding from system enablement. The frontend propagates that state to profile registration and secure verification, selects usable methods with fallback logic, and adds localized system-disabled messaging.

Changes

Passkey availability and verification

Layer / File(s) Summary
Separate credential and system status
controller/passkey.go, controller/passkey_test.go
PasskeyStatus returns independent enabled and system_enabled values, with tests covering credential and system-setting combinations.
Propagate system availability to profile UI
web/default/src/features/auth/passkey/*, web/default/src/features/profile/..., web/default/src/i18n/locales/*
The hook exposes systemEnabled; the profile card blocks registration when disabled, shows administrator messaging, and adds translated labels.
Centralize verification method selection
web/default/src/features/auth/secure-verification/lib/*
selectVerificationMethod prioritizes usable preferred methods, falls back between Passkey and 2FA, and is covered by tests.
Apply availability-aware verification flow
web/default/src/features/auth/secure-verification/api.ts, web/default/src/features/auth/secure-verification/hooks/*, web/default/src/features/auth/secure-verification/components/*
Secure verification requires both Passkey availability flags, uses centralized selection, and falls back to an available dialog tab.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: calcium-ion, seefs001

Poem

I’m a rabbit with keys in my ear,
Now system and binding are clear.
If Passkey can’t play,
Two-Factor leads the way,
While settings glow bright for admins here.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: making secure verification respect passkey availability.
Linked Issues check ✅ Passed The changes address both linked requirements by falling back to 2FA when Passkey is unusable and blocking passkey registration when the system setting is off.
Out of Scope Changes check ✅ Passed The added tests, helper, and locale updates all support the passkey availability fix and do not introduce unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
web/default/src/features/auth/passkey/hooks/use-passkey-management.ts (1)

175-175: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor inconsistency: systemEnabled skips useMemo used by sibling derived values.

enabled and lastUsed both wrap derivation in useMemo, but systemEnabled does not. Harmless since it's a cheap read, but inconsistent style.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/auth/passkey/hooks/use-passkey-management.ts` at
line 175, Update the systemEnabled derivation in the passkey management hook to
use useMemo consistently with the sibling enabled and lastUsed values,
preserving the existing status?.system_enabled ?? null result.
web/default/src/features/auth/secure-verification/hooks/use-secure-verification.ts (1)

211-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse selectVerificationMethod for recommendedMethod to eliminate duplicated logic.

recommendedMethod duplicates the same fallback logic as selectVerificationMethod without a preferred method. Reusing the helper ensures a single source of truth for method selection.

♻️ Proposed refactor
   const recommendedMethod = useMemo<VerificationMethod | null>(() => {
-    if (methods.hasPasskey && methods.passkeySupported) return 'passkey'
-    if (methods.has2FA) return '2fa'
-    return null
+    return selectVerificationMethod(methods)
   }, [methods])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/auth/secure-verification/hooks/use-secure-verification.ts`
around lines 211 - 215, Update the recommendedMethod useMemo to call
selectVerificationMethod without a preferred method instead of duplicating the
passkey, 2FA, and null fallback checks. Preserve the existing memoization and
methods dependency.
web/default/src/features/auth/secure-verification/lib/select-verification-method.test.ts (1)

19-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use Vitest instead of node:test for this test file.

As per coding guidelines, web/default/**/*.test.ts files should use Vitest. This file uses node:test and node:assert/strict, which may not be picked up by the project's Vitest test runner or CI pipeline. Consider migrating to Vitest's describe, test, and expect APIs.

Additionally, consider adding table-style tests covering these missing invariants:

  • No preferredMethod with passkey available → returns 'passkey' (default fallback)
  • No preferredMethod with only 2FA → returns '2fa'
  • Preferred '2fa' with only passkey available → falls back to 'passkey'
♻️ Proposed migration to Vitest with table tests
-import assert from 'node:assert/strict'
-import { describe, test } from 'node:test'
-
-import { selectVerificationMethod } from './select-verification-method'
-
-describe('selectVerificationMethod', () => {
-  test('falls back to 2FA when the preferred Passkey is unavailable', () => {
-    assert.equal(
-      selectVerificationMethod(
-        { has2FA: true, hasPasskey: false, passkeySupported: true },
-        'passkey'
-      ),
-      '2fa'
-    )
-  })
-
-  test('falls back to 2FA when a bound Passkey is unsupported by the device', () => {
-    assert.equal(
-      selectVerificationMethod(
-        { has2FA: true, hasPasskey: true, passkeySupported: false },
-        'passkey'
-      ),
-      '2fa'
-    )
-  })
-
-  test('uses an available preferred method', () => {
-    assert.equal(
-      selectVerificationMethod(
-        { has2FA: true, hasPasskey: true, passkeySupported: true },
-        '2fa'
-      ),
-      '2fa'
-    )
-    assert.equal(
-      selectVerificationMethod(
-        { has2FA: true, hasPasskey: true, passkeySupported: true },
-        'passkey'
-      ),
-      'passkey'
-    )
-  })
-
-  test('returns null when no usable method is available', () => {
-    assert.equal(
-      selectVerificationMethod({
-        has2FA: false,
-        hasPasskey: true,
-        passkeySupported: false,
-      }),
-      null
-    )
-  })
-})
+import { describe, expect, it } from 'vitest'
+
+import type { VerificationMethod, VerificationMethods } from '../types'
+import { selectVerificationMethod } from './select-verification-method'
+
+describe('selectVerificationMethod', () => {
+  const cases: Array<{
+    name: string
+    methods: VerificationMethods
+    preferred?: VerificationMethod
+    expected: VerificationMethod | null
+  }> = [
+    {
+      name: 'falls back to 2FA when the preferred Passkey is unavailable',
+      methods: { has2FA: true, hasPasskey: false, passkeySupported: true },
+      preferred: 'passkey',
+      expected: '2fa',
+    },
+    {
+      name: 'falls back to 2FA when a bound Passkey is unsupported by the device',
+      methods: { has2FA: true, hasPasskey: true, passkeySupported: false },
+      preferred: 'passkey',
+      expected: '2fa',
+    },
+    {
+      name: 'uses an available preferred 2FA',
+      methods: { has2FA: true, hasPasskey: true, passkeySupported: true },
+      preferred: '2fa',
+      expected: '2fa',
+    },
+    {
+      name: 'uses an available preferred Passkey',
+      methods: { has2FA: true, hasPasskey: true, passkeySupported: true },
+      preferred: 'passkey',
+      expected: 'passkey',
+    },
+    {
+      name: 'defaults to Passkey when no preference and Passkey is available',
+      methods: { has2FA: true, hasPasskey: true, passkeySupported: true },
+      expected: 'passkey',
+    },
+    {
+      name: 'defaults to 2FA when no preference and only 2FA is available',
+      methods: { has2FA: true, hasPasskey: false, passkeySupported: true },
+      expected: '2fa',
+    },
+    {
+      name: 'falls back to Passkey when preferred 2FA is unavailable',
+      methods: { has2FA: false, hasPasskey: true, passkeySupported: true },
+      preferred: '2fa',
+      expected: 'passkey',
+    },
+    {
+      name: 'returns null when no usable method is available',
+      methods: { has2FA: false, hasPasskey: true, passkeySupported: false },
+      expected: null,
+    },
+  ]
+
+  for (const { name, methods, preferred, expected } of cases) {
+    it(name, () => {
+      expect(selectVerificationMethod(methods, preferred)).toBe(expected)
+    })
+  }
+})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/auth/secure-verification/lib/select-verification-method.test.ts`
around lines 19 - 72, Replace the node:test and node:assert/strict imports in
selectVerificationMethod tests with Vitest’s describe, test, and expect APIs.
Extend the selectVerificationMethod coverage with table-style cases for default
passkey fallback, default 2FA fallback, and preferred-2FA fallback to passkey
when only passkey is usable.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@web/default/src/features/auth/passkey/hooks/use-passkey-management.ts`:
- Line 175: Update the systemEnabled derivation in the passkey management hook
to use useMemo consistently with the sibling enabled and lastUsed values,
preserving the existing status?.system_enabled ?? null result.

In
`@web/default/src/features/auth/secure-verification/hooks/use-secure-verification.ts`:
- Around line 211-215: Update the recommendedMethod useMemo to call
selectVerificationMethod without a preferred method instead of duplicating the
passkey, 2FA, and null fallback checks. Preserve the existing memoization and
methods dependency.

In
`@web/default/src/features/auth/secure-verification/lib/select-verification-method.test.ts`:
- Around line 19-72: Replace the node:test and node:assert/strict imports in
selectVerificationMethod tests with Vitest’s describe, test, and expect APIs.
Extend the selectVerificationMethod coverage with table-style cases for default
passkey fallback, default 2FA fallback, and preferred-2FA fallback to passkey
when only passkey is usable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49bdf770-61c6-4621-b5cc-392854fa29ab

📥 Commits

Reviewing files that changed from the base of the PR and between 7c28993 and af36501.

📒 Files selected for processing (17)
  • controller/passkey.go
  • controller/passkey_test.go
  • web/default/src/features/auth/passkey/hooks/use-passkey-management.ts
  • web/default/src/features/auth/passkey/types.ts
  • web/default/src/features/auth/secure-verification/api.ts
  • web/default/src/features/auth/secure-verification/components/secure-verification-dialog.tsx
  • web/default/src/features/auth/secure-verification/hooks/use-secure-verification.ts
  • web/default/src/features/auth/secure-verification/lib/select-verification-method.test.ts
  • web/default/src/features/auth/secure-verification/lib/select-verification-method.ts
  • web/default/src/features/profile/components/passkey-card.tsx
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh-TW.json
  • web/default/src/i18n/locales/zh.json

@Imzl-zl

Imzl-zl commented Jul 13, 2026

Copy link
Copy Markdown
Author

已复核 CodeRabbit 的 3 条 nitpick,并在 31e483350 中完成有效修订:

  • recommendedMethod 现在复用 selectVerificationMethod,消除重复的回退逻辑。
  • ✅ 选择器测试改为表格驱动,并补齐默认 Passkey、默认 2FA、首选 2FA 不可用时回退 Passkey 等场景;共 8 项通过。
  • ↪️ 未迁移 Vitest:当前 web/default/package.json 没有 Vitest 依赖,仓库现有前端测试统一使用 node:test,且由 bun test 正常执行。
  • ↪️ 未给 systemEnabled 增加 useMemo:该值是简单的 primitive 派生值。根据 React 性能规范,反而移除了相邻 enabled / lastUsed 的无效 memoization,保持一致且减少开销。

验证结果:

  • bun test .../select-verification-method.test.ts:8 pass
  • bun run typecheck:通过
  • 变更文件定向 oxlint / oxfmt --check:通过
  • git diff --check:通过

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Passkey 不可用时安全验证仍首选 Passkey,且个人资料仍允许注册

1 participant