[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>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 09:22:53 +03:00
parent c2c659ef62
commit a77b3f8a59
35 changed files with 3624 additions and 468 deletions
@@ -0,0 +1,65 @@
# Module: Azaion.Services.SessionService
## Purpose
Logout / revocation surface and the verifier-poll snapshot. Distinct from `RefreshTokenService` (which rotates and reuse-detects); this service expresses the human / admin / system intent to kill a session and exposes the cross-service denylist feed.
> Added in cycle 2 (2026-05-14) by AZ-535 (Epic AZ-529). The `RevokeMissionsForAircraft` path was added the same day for AZ-533 (mission-token auto-revoke on reconnect).
## Public Interface
### ISessionService
| Method | Signature | Description |
|--------|-----------|-------------|
| `RevokeBySid` | `Task<bool> RevokeBySid(Guid sessionId, Guid? byUserId, string reason, CancellationToken ct = default)` | Revoke a single session by id. Returns `true` if the session was already revoked (no-op), `false` if this call performed the revocation. Throws `BusinessException(SessionNotFound)` if no row exists. |
| `RevokeAllForUser` | `Task<int> RevokeAllForUser(Guid userId, Guid? byUserId, string reason, CancellationToken ct = default)` | Revoke every active session for a user. Returns the number of rows newly revoked. |
| `RevokeMissionsForAircraft` | `Task<int> RevokeMissionsForAircraft(Guid aircraftId, CancellationToken ct = default)` | AZ-533 — auto-revoke every open mission session for an aircraft. Fired on successful `/login` or `/token/refresh` from a `CompanionPC` user. |
| `GetRevokedSince` | `Task<IReadOnlyList<RevokedSession>> GetRevokedSince(DateTime since, CancellationToken ct = default)` | Verifier-poll snapshot. Returns sessions revoked after `since` whose `exp` is still in the future (auto-prunes already-expired entries). |
### `record RevokedSession(Guid Sid, DateTime Exp, DateTime RevokedAt, string? Reason)`
Shape returned by `GetRevokedSince`. Field names match the JSON the `/sessions/revoked` endpoint serializes to verifiers.
## Internal Logic
- **Revocation reasons** are constants on `SessionRevokedReasons` (`logged_out`, `logged_out_all`, `admin_revoked`, `post_flight_reconnect`, `rotated`, `reuse_detected`, `family_revoked`).
- **Idempotency** — `RevokeBySid` reads first, then writes only if `revoked_at IS NULL`. The boolean return signals which side of the race the caller was on.
- **Mission auto-revoke** uses the partial index `sessions_aircraft_active_idx` (defined in `09_sessions_logout_and_mission.sql`) — O(active mission rows for that aircraft).
- **Snapshot pruning** — `GetRevokedSince` filters `expires_at > now()` so the response stays bounded even if revocation history grows large; the endpoint additionally clamps `since` to `now - 12 h` to prevent unbounded historical scans.
## Dependencies
- `IDbFactory` — admin connection for updates, read connection for the snapshot
- `Session` entity, `SessionRevokedReasons`, `SessionClasses` constants
- `BusinessException` / `ExceptionEnum.SessionNotFound`
## Consumers
- `Program.cs` `/logout``RevokeBySid`
- `Program.cs` `/logout/all``RevokeAllForUser`
- `Program.cs` `/sessions/{sid}/revoke` (admin-only) → `RevokeBySid`
- `Program.cs` `/sessions/revoked` (verifier-poll, gated by `revocationReaderPolicy`) → `GetRevokedSince`
- `Program.cs` `/login` and `/token/refresh` (when caller is `RoleEnum.CompanionPC`) → `RevokeMissionsForAircraft`
## Data Models
Operates on the `Session` entity via `AzaionDb.Sessions` table.
## Configuration
None directly. The `/sessions/revoked` endpoint hard-codes the 12-hour `since` floor; review if mission TTL is ever raised above 12 h.
## External Integrations
PostgreSQL via `IDbFactory`.
## Security
- The verifier-poll endpoint is gated by `revocationReaderPolicy` (`Service` or `ApiAdmin` role). Each verifier deployment (satellite-provider, gps-denied, ui) provisions one `Role=Service` user.
- The `Cache-Control: no-cache` header on `/sessions/revoked` prevents intermediaries from staleing the denylist.
- The `revoked_by_user_id` column gives an audit trail of "who revoked this session" for admin and user-initiated revocations; system revocations (rotation, reuse, post-flight) leave it null on purpose.
## Tests
- `e2e/Azaion.E2E/Tests/LogoutTests.cs` — covers AC-1 (logout revokes session), AC-2 (logout/all), AC-3 (admin revoke), AC-4 (snapshot recent + prune expired), AC-5 (idempotent logout).
- `e2e/Azaion.E2E/Tests/MissionTokenTests.cs` — exercises `RevokeMissionsForAircraft` via AC-4 (auto-revoke on reconnect).