From 72e00f3a699e7979d9994f7dd6c5db4b6cd0120f Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Fri, 27 Feb 2026 04:59:29 +0800 Subject: [PATCH] Fix QA review #2: backup code flow, audit filters, schema hardening C-01: verifyTotp now sends backup_code field when in backup mode C-02: Backup code input filter allows alphanumeric chars (not digits only) W-01: Audit log ACTION_TYPES aligned with actual backend action strings W-02: Added extra="forbid" to SetupRequest and LoginRequest schemas Co-Authored-By: Claude Opus 4.6 --- backend/app/schemas/auth.py | 4 +++ frontend/src/components/admin/ConfigPage.tsx | 29 ++++++++++---------- frontend/src/components/auth/LockScreen.tsx | 4 +-- frontend/src/hooks/useAuth.ts | 13 +++++---- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/backend/app/schemas/auth.py b/backend/app/schemas/auth.py index a45dba3..41de744 100644 --- a/backend/app/schemas/auth.py +++ b/backend/app/schemas/auth.py @@ -32,6 +32,8 @@ def _validate_username(v: str) -> str: class SetupRequest(BaseModel): + model_config = ConfigDict(extra="forbid") + username: str password: str @@ -69,6 +71,8 @@ class RegisterRequest(BaseModel): class LoginRequest(BaseModel): + model_config = ConfigDict(extra="forbid") + username: str password: str diff --git a/frontend/src/components/admin/ConfigPage.tsx b/frontend/src/components/admin/ConfigPage.tsx index 0c1460b..afd00be 100644 --- a/frontend/src/components/admin/ConfigPage.tsx +++ b/frontend/src/components/admin/ConfigPage.tsx @@ -16,21 +16,20 @@ import { cn } from '@/lib/utils'; import { actionColor } from './shared'; const ACTION_TYPES = [ - 'user.create', - 'user.login', - 'user.logout', - 'user.login_failed', - 'user.locked', - 'user.unlocked', - 'user.role_changed', - 'user.disabled', - 'user.enabled', - 'user.password_reset', - 'user.totp_disabled', - 'user.mfa_enforced', - 'user.mfa_enforcement_removed', - 'user.sessions_revoked', - 'config.updated', + 'admin.user_created', + 'admin.role_changed', + 'admin.password_reset', + 'admin.mfa_disabled', + 'admin.mfa_enforce_toggled', + 'admin.user_deactivated', + 'admin.user_activated', + 'admin.sessions_revoked', + 'admin.config_updated', + 'auth.login_success', + 'auth.login_failed', + 'auth.setup_complete', + 'auth.registration', + 'auth.mfa_enforce_prompted', ]; function actionLabel(action: string): string { diff --git a/frontend/src/components/auth/LockScreen.tsx b/frontend/src/components/auth/LockScreen.tsx index 1885512..12aaa46 100644 --- a/frontend/src/components/auth/LockScreen.tsx +++ b/frontend/src/components/auth/LockScreen.tsx @@ -145,7 +145,7 @@ export default function LockScreen() { const handleTotpSubmit = async (e: FormEvent) => { e.preventDefault(); try { - await verifyTotp(totpCode); + await verifyTotp({ code: totpCode, isBackup: useBackupCode }); } catch (error) { toast.error(getErrorMessage(error, 'Invalid verification code')); setTotpCode(''); @@ -257,7 +257,7 @@ export default function LockScreen() { onChange={(e) => setTotpCode( useBackupCode - ? e.target.value.replace(/[^0-9-]/g, '') + ? e.target.value.replace(/[^A-Za-z0-9-]/g, '').toUpperCase() : e.target.value.replace(/\D/g, '') ) } diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index 239fe2f..098d268 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -57,11 +57,14 @@ export function useAuth() { }); const totpVerifyMutation = useMutation({ - mutationFn: async (code: string) => { - const { data } = await api.post('/auth/totp-verify', { - mfa_token: mfaToken, - code, - }); + mutationFn: async ({ code, isBackup }: { code: string; isBackup: boolean }) => { + const payload: Record = { mfa_token: mfaToken! }; + if (isBackup) { + payload.backup_code = code; + } else { + payload.code = code; + } + const { data } = await api.post('/auth/totp-verify', payload); return data; }, onSuccess: () => {