fix(auth): allow backup codes for TOTP recovery

This commit is contained in:
Jens Kowalsky
2026-10-14 22:13:52 +02:00
committed by Maurice
parent bda24ec639
commit a36019b7e2
5 changed files with 50 additions and 8 deletions
+10 -3
View File
@@ -871,11 +871,18 @@ export class AuthService {
throw new DomainError(401, 'Incorrect password');
}
const secret = decryptMfaSecret(user.mfa_secret);
const tokenStr = String(code).replace(/\s/g, '');
const ok = authenticator.verify({ token: tokenStr, secret });
if (!ok) {
const tokenStr = String(code).trim();
const okTotp = authenticator.verify({ token: tokenStr.replace(/\s/g, ''), secret });
// Recovery codes already work at login. Accept them here too, but only
// after the account password has been verified and while the existing
// rate limit / instance MFA policy allow this operation.
const okRecovery = !okTotp && parseBackupCodeHashes(user.mfa_backup_codes)
.some((hash) => matchBackupCode(tokenStr, hash));
if (!okTotp && !okRecovery) {
throw new DomainError(401, 'Invalid verification code');
}
// Disabling MFA clears the old secret and *all* backup codes atomically.
// A recovery code used here cannot be replayed after re-enrolment.
// Every other session ends with the second factor; this one just proved both.
await this.uow.transactional(async () => {
await this.usersRepo.disableMfa(userId);