security: MFA backup codes — issue on enable, redeem at login, regenerate on demand (#43)
CI / Architecture Guardrails (push) Successful in 6m1s
CI / Assistant Split Regression (push) Successful in 6m52s
CI / Lint (push) Successful in 8m40s
CI / Typecheck (push) Successful in 9m45s
CI / Unit Tests (push) Successful in 7m28s
CI / Build (push) Failing after 10m16s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Architecture Guardrails (push) Successful in 6m1s
CI / Assistant Split Regression (push) Successful in 6m52s
CI / Lint (push) Successful in 8m40s
CI / Typecheck (push) Successful in 9m45s
CI / Unit Tests (push) Successful in 7m28s
CI / Build (push) Failing after 10m16s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
Adds a one-time-use backup code set so users with a lost authenticator are not locked out. Codes are Crockford base32 (XXXXX-XXXXX), hashed with argon2id, and redeemed under a WHERE-guarded delete so a concurrent replay race fails closed. - New MfaBackupCode model + migration - Issue 10 codes inside the enable transaction; show plaintext exactly once - Sign-in page accepts TOTP or backup code, reporting remaining count - regenerateBackupCodes tRPC mutation wipes + reissues atomically - Unit coverage for generator, normalizer, verify, redeem, and race path Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,7 @@ import {
|
||||
saveDashboardLayout,
|
||||
SetColumnPreferencesInputSchema,
|
||||
setColumnPreferences,
|
||||
regenerateBackupCodes,
|
||||
ToggleFavoriteProjectInputSchema,
|
||||
toggleFavoriteProject,
|
||||
verifyAndEnableTotp as verifyAndEnableTotpSelfService,
|
||||
@@ -152,4 +153,7 @@ export const userRouter = createTRPCRouter({
|
||||
|
||||
/** Get MFA status for the current user. */
|
||||
getMfaStatus: protectedProcedure.query(({ ctx }) => getCurrentMfaStatus(ctx)),
|
||||
|
||||
/** Generate a fresh set of MFA backup codes, invalidating any previous set. */
|
||||
regenerateBackupCodes: protectedProcedure.mutation(({ ctx }) => regenerateBackupCodes(ctx)),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user