# Module: Azaion.Common.Entities.AuditEvent ## Purpose Append-only audit row for security-relevant events: login outcomes, lockouts, and the MFA enrollment / login lifecycle. Drives both the per-account sliding-window rate limit (AZ-537) and the human-readable security trail. > Added in cycle 2 (2026-05-14). Initial event types from AZ-537 (login_failed / login_success / login_lockout); MFA event types added by AZ-534 in the same cycle. ## Public Interface ### AuditEvent | Property | Type | Description | |----------|------|-------------| | `Id` | `long` | DB-assigned identity. | | `EventType` | `string` | One of `AuditEventTypes`. | | `OccurredAt` | `DateTime` | `now()` at insert. | | `Email` | `string?` | Normalised lowercase. NULL for system events without a subject. | | `Ip` | `string?` | Caller IP from `HttpContext.Connection.RemoteIpAddress`. NULL for background tasks. | | `Metadata` | `string?` | Reserved for future structured payload. Not used today. | ### AuditEventTypes (constants) | Value | When | |-------|------| | `login_failed` | Wrong password, locked account, or rate-limit reject. | | `login_lockout` | Account just hit `MaxAttempts` and was locked. | | `login_success` | Password verified, MFA not required. | | `mfa_enroll` | `/users/me/mfa/enroll` succeeded. | | `mfa_confirm` | `/users/me/mfa/confirm` succeeded; MFA now active. | | `mfa_disable` | `/users/me/mfa/disable` succeeded. | | `mfa_login_success` | `/login/mfa` succeeded with TOTP. | | `mfa_login_failed` | `/login/mfa` rejected (bad TOTP and bad recovery code). | | `mfa_recovery_used` | `/login/mfa` succeeded with a recovery code (also burns the code). | ## Internal Logic None — pure data class. All write/read logic lives in `AuditLog`. ## Dependencies None. ## Consumers - `AuditLog` — produces every row; reads via `CountRecentFailedLogins`. - `AzaionDb.AuditEvents` — `ITable` access. - `AzaionDbSchemaHolder` — maps `AuditEvent` to the `audit_events` table. ## Data Models Maps to PostgreSQL table `audit_events` (defined in `env/db/07_auth_lockout_and_audit.sql`). Columns: `id (bigserial PK)`, `event_type (varchar(64))`, `occurred_at (timestamp default now())`, `email (varchar(160) NULL)`, `ip (varchar(64) NULL)`, `metadata (text NULL)`. Index: `audit_events_event_type_email_idx (event_type, email, occurred_at DESC)` — supports the per-account sliding-window failed-login count in O(window-rows). ## Configuration None. ## External Integrations None. ## Security - Append-only by convention — `azaion_admin` only has `INSERT, SELECT` on the table. - Stores PII (email, IP); access is gated to `azaion_admin` and `azaion_reader` only. No public endpoint surfaces audit rows. - The table backs CMMC AC.L2-3.1.8 ("limit unsuccessful logon attempts") — tampering with it bypasses the rate limit + lockout enforcement. ## Tests Indirectly tested via `RateLimitLockoutTests`, `MfaEnrollmentTests`, `MfaLoginTests` (assertions on the resulting `audit_events` rows).