fix(auth): lock accounts after failed attempts, one-time TOTP codes, stronger MFA changes
- Per-account lockout: five failed attempts (password, MFA code or a step-up confirmation) lock the account for 15 minutes, on top of the per-IP throttle - guesses spread over many addresses no longer help. - A TOTP code is accepted once: the time step is stored and that step or any earlier one is refused (also between two concurrent requests). - Login runs bcrypt on every path and answers unknown email, wrong password, deactivated and locked accounts with the same message; failed logins for unknown emails no longer write attacker-chosen text into the audit log. - Turning MFA off needs password and a current code; turning it on needs the password. Both end all other sessions. - New audit action ACCOUNT_LOCKED. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
79d20ee64e
commit
5c666fe49a
22 files changed
+494
-91
No files matched your search
@@ -2,7 +2,12 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as QRCode from 'qrcode';
|
||||
import { decryptMfaSecret, encryptMfaSecret } from './mfa-crypto.util';
|
||||
import { buildTotpUri, generateTotpSecret, verifyTotpCode } from './totp.util';
|
||||
import {
|
||||
buildTotpUri,
|
||||
generateTotpSecret,
|
||||
verifyTotpCode,
|
||||
verifyTotpStep,
|
||||
} from './totp.util';
|
||||
import {
|
||||
TOKEN_AUDIENCE,
|
||||
jwtSignOptions,
|
||||
@@ -104,6 +109,21 @@ export class MfaService {
|
||||
return { userId: payload.sub };
|
||||
}
|
||||
|
||||
/** See verifyTotpStep; use AccountGuardService.consumeTotp for logins. */
|
||||
async verifyCodeStep(
|
||||
encryptedSecret: string,
|
||||
email: string,
|
||||
code: string,
|
||||
afterTimeStep?: number,
|
||||
): Promise<number | null> {
|
||||
return verifyTotpStep(
|
||||
decryptMfaSecret(encryptedSecret),
|
||||
email,
|
||||
code,
|
||||
afterTimeStep,
|
||||
);
|
||||
}
|
||||
|
||||
async verifyCode(
|
||||
encryptedSecret: string,
|
||||
email: string,
|
||||
|
||||
Reference in new issue
Block a user