Files
Oleksandr Bezdieniezhnykh a77b3f8a59 [AZ-529] [AZ-530] Cycle-2 documentation refresh
Refreshes _docs/02_document/ to reflect the cycle-2 auth-modernization
+ CMMC hardening landings (AZ-531..AZ-538). Authoritative source for
the ripple set is ripple_log_cycle2.md.

Covered:
- architecture.md (section 1 rewritten, ADRs 6-9 added)
- data_model.md (sessions, audit_events, user columns, migrations)
- system-flows.md (F1 rewritten; F11-F17 added; F2/F7/F9 minor)
- module-layout.md (cycle-2 sub-component table)
- diagrams/flows/flow_login.md (dual-token + MFA)
- components/{01_data_layer,03_auth_and_security,05_admin_api}
- modules/ (12 new, 8 modified — full Argon2id/ES256/MFA/refresh
  /mission/session/audit/jwks rollup)
- tests/{blackbox,security,traceability-matrix}

Step 13 (Update Docs) output for cycle 2.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 09:22:53 +03:00

3.7 KiB

Module: Azaion.Services.AuditLog

Purpose

Append-only audit trail for security-relevant events (login attempts, lockouts, MFA lifecycle). Also exposes the per-account sliding-window failed-login count consumed by UserService.ValidateUser's rate limit.

Added in cycle 2 (2026-05-14). Initially shipped with AZ-537 (login lockout + per-account rate-limit feed); MFA event types added by AZ-534 in the same cycle.

Public Interface

IAuditLog

Method Signature Description
RecordLoginFailed Task RecordLoginFailed(string email, CancellationToken ct = default) Inserts audit_events row with event_type='login_failed'.
RecordLoginLockout Task RecordLoginLockout(string email, CancellationToken ct = default) Inserts event_type='login_lockout' (AZ-537 AC-6).
RecordLoginSuccess Task RecordLoginSuccess(string email, CancellationToken ct = default) Inserts event_type='login_success'.
RecordMfaEnroll / RecordMfaConfirm / RecordMfaDisable Task ...(string email, CancellationToken ct = default) MFA enrollment lifecycle.
RecordMfaLoginSuccess / RecordMfaLoginFailed / RecordMfaRecoveryUsed Task ...(string email, CancellationToken ct = default) MFA login outcomes.
CountRecentFailedLogins Task<int> CountRecentFailedLogins(string email, int windowSeconds, CancellationToken ct = default) Number of login_failed rows for the email within the last windowSeconds. Drives the per-account sliding-window rate limit (AZ-537 AC-2).

Internal Logic

  • Email normalisation — every insert and read lowercases the email (ToLowerInvariant) so case-variant addresses can't bypass the rate limit.
  • IP capture — pulls HttpContext.Connection.RemoteIpAddress via IHttpContextAccessor. Null when there is no current request (background task). Null IPs are persisted as null, not omitted.
  • Insert path uses dbFactory.RunAdmin (write privilege required); count uses dbFactory.Run (read-only).
  • Backing tablepublic.audit_events, defined by env/db/07_auth_lockout_and_audit.sql. Supporting index audit_events_event_type_email_idx (event_type, email, occurred_at DESC) makes the per-account sliding-window count O(window-rows).

Dependencies

  • IDbFactory — read + admin connections
  • IHttpContextAccessor — for the request IP
  • AuditEvent entity, AuditEventTypes constants

Consumers

  • UserService.ValidateUser — calls CountRecentFailedLogins (per-account rate limit), RecordLoginFailed, RecordLoginSuccess, RecordLoginLockout.
  • MfaService — calls every RecordMfa* method along the enroll/confirm/disable/login paths.

Data Models

Operates on the AuditEvent entity via AzaionDb.AuditEvents table.

Configuration

None directly. The window/threshold constants live on AuthConfig.RateLimit and AuthConfig.Lockout, consumed by the caller (UserService.ValidateUser).

External Integrations

PostgreSQL via IDbFactory.

Security

  • Append-only by convention — no UPDATE/DELETE in code, and azaion_admin only has INSERT, SELECT on the table.
  • The IP and email are PII; access to the table is gated to azaion_admin (insert + read) and azaion_reader (read-only). No public endpoint surfaces audit rows directly.
  • The per-account sliding-window count is the foundation of CMMC AC.L2-3.1.8 enforcement; tampering with audit_events bypasses the rate limit.

Tests

  • e2e/Azaion.E2E/Tests/RateLimitLockoutTests.cs — exercises RecordLoginFailed + CountRecentFailedLogins end-to-end via the lockout/rate-limit ACs.
  • e2e/Azaion.E2E/Tests/MfaEnrollmentTests.cs and MfaLoginTests.cs — assert the corresponding MFA audit_events rows after each lifecycle event.