mirror of
https://github.com/azaion/admin.git
synced 2026-06-22 07:21:10 +00:00
[AZ-556] [AZ-557] Unify login errors + share MFA lockout pipeline
AZ-556 collapses every /login rejection (unknown email, wrong password, disabled account, lockout, per-account rate limit) to a single opaque InvalidCredentials (70) → 401 response. Timing equalised by a new Security.VerifyDummy using the same Argon2id parameters. Audit log keeps the rejection category internally (login_failed_unknown_email, login_failed_disabled). AZ-557 wires /login/mfa into the existing per-account lockout + rate-limit pipeline. MFA failures now feed UserService's shared failure accounting (RegisterMfaFailedLogin → RegisterFailedLoginCore) and CountRecentFailedLogins aggregates both login_failed and mfa_login_failed rows. Successful TOTP / recovery resets the counter. Deprecated five legacy ExceptionEnum members (NoEmailFound, WrongPassword, UserDisabled, AccountLocked, LoginRateLimited) — kept defined for cross-workspace verifier compatibility during the deprecation window. E2E coverage updated: AuthTests (byte-identical body assertion + disabled-account audit row), LoginRateLimitTests, PasswordHashingTests, SecurityTests, plus four new MfaLoginTests (AC1, AC2, AC5, AC7). Code review verdict: PASS_WITH_WARNINGS (batch_06_cycle2_review.md). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -117,12 +117,13 @@ public sealed class PasswordHashingTests
|
||||
using var legacyResp = await client.PostAsync("/login", new { email = legacyEmail, password = wrong });
|
||||
using var argon2Resp = await client.PostAsync("/login", new { email = argon2Email, password = wrong });
|
||||
|
||||
// Assert
|
||||
// Assert — AZ-556 unified the wire response across all rejection categories;
|
||||
// both hash formats now return the same opaque InvalidCredentials.
|
||||
foreach (var resp in new[] { legacyResp, argon2Resp })
|
||||
{
|
||||
resp.StatusCode.Should().Be(HttpStatusCode.Conflict);
|
||||
resp.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
var err = await resp.Content.ReadFromJsonAsync<ErrorResponse>(ResponseJsonOptions);
|
||||
err!.ErrorCode.Should().Be(30, "WrongPassword == 30");
|
||||
err!.ErrorCode.Should().Be(70, "InvalidCredentials == 70 (AZ-556)");
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -176,7 +177,8 @@ public sealed class PasswordHashingTests
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
using var r = await client.PostAsync("/login", new { email, password = pwd });
|
||||
sw.Stop();
|
||||
r.StatusCode.Should().Be(HttpStatusCode.Conflict, "wrong password");
|
||||
r.StatusCode.Should().Be(HttpStatusCode.Unauthorized,
|
||||
"AZ-556 — wrong-password is now InvalidCredentials (401)");
|
||||
samples.Add((len, sw.Elapsed.TotalMilliseconds));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user