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 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-02-27 04:59:29 +08:00
parent 72ac1d53fb
commit 72e00f3a69
4 changed files with 28 additions and 22 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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, '')
)
}

View File

@ -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<string, string> = { 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: () => {