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
@@ -9,6 +9,7 @@ const AUDIT_ACTION_LABELS: Record<string, string> = {
|
||||
TOKEN_REFRESHED: "Token erneuert",
|
||||
MFA_ENABLED: "MFA aktiviert",
|
||||
MFA_DISABLED: "MFA deaktiviert",
|
||||
ACCOUNT_LOCKED: "Konto vorübergehend gesperrt",
|
||||
};
|
||||
|
||||
export function auditActionLabel(action: string): string {
|
||||
|
||||
@@ -65,16 +65,20 @@ export function enrollMfa(): Promise<MfaEnrollment> {
|
||||
return apiFetch<MfaEnrollment>("/profile/mfa/enroll", { method: "POST" });
|
||||
}
|
||||
|
||||
export function confirmMfaEnrollment(setupToken: string, code: string): Promise<void> {
|
||||
export function confirmMfaEnrollment(
|
||||
setupToken: string,
|
||||
code: string,
|
||||
currentPassword: string,
|
||||
): Promise<void> {
|
||||
return apiFetch<void>("/profile/mfa/enroll/confirm", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ setupToken, code }),
|
||||
body: JSON.stringify({ setupToken, code, currentPassword }),
|
||||
});
|
||||
}
|
||||
|
||||
export function disableMfa(currentPassword: string): Promise<void> {
|
||||
export function disableMfa(currentPassword: string, mfaCode: string): Promise<void> {
|
||||
return apiFetch<void>("/profile/mfa/disable", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ currentPassword }),
|
||||
body: JSON.stringify({ currentPassword, mfaCode }),
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user