Files
admin/Azaion.Common/Entities/AuditEvent.cs
T
Oleksandr Bezdieniezhnykh 4bf2e689cb [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>
2026-05-14 09:56:00 +03:00

33 lines
1.4 KiB
C#

namespace Azaion.Common.Entities;
public class AuditEvent
{
public long Id { get; set; }
public string EventType { get; set; } = null!;
public DateTime OccurredAt { get; set; }
public string? Email { get; set; }
public string? Ip { get; set; }
public string? Metadata { get; set; }
}
public static class AuditEventTypes
{
public const string LoginFailed = "login_failed";
public const string LoginLockout = "login_lockout";
public const string LoginSuccess = "login_success";
// AZ-556 — per-category internal forensics for unified `InvalidCredentials` wire
// response. SecOps can distinguish these in the audit_events table even though the
// /login response cannot be distinguished by an attacker.
public const string LoginFailedUnknownEmail = "login_failed_unknown_email";
public const string LoginFailedDisabled = "login_failed_disabled";
// AZ-534 — MFA lifecycle + login events.
public const string MfaEnroll = "mfa_enroll";
public const string MfaConfirm = "mfa_confirm";
public const string MfaDisable = "mfa_disable";
public const string MfaLoginSuccess = "mfa_login_success";
public const string MfaLoginFailed = "mfa_login_failed";
public const string MfaRecoveryUsed = "mfa_recovery_used";
}