mirror of
https://github.com/azaion/admin.git
synced 2026-06-21 17:11:09 +00:00
[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:
@@ -0,0 +1,69 @@
|
||||
# Module: Azaion.Services.MissionTokenService
|
||||
|
||||
## Purpose
|
||||
Issues long-lived (≤ 12 h) single-use access tokens for offline UAV missions. Distinct from `AuthService.CreateToken` because:
|
||||
- Lifetime is per-mission (`planned_duration_h + 1 h` buffer), not the 15-minute interactive policy.
|
||||
- Audience is narrowed to `satellite-provider`, not the broad admin audience.
|
||||
- No refresh: a single token covers the entire flight, then dies.
|
||||
- Carries mission-specific claims (`mission_id`, `aircraft_id`, `valid_region`, `permissions`).
|
||||
|
||||
> Added in cycle 2 (2026-05-14) by AZ-533 (Epic AZ-529). Solves the "10 h offline UAV vs. 15 min interactive access token" tension without weakening interactive-session security.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### IMissionTokenService
|
||||
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `Issue` | `Task<MissionSessionResponse> Issue(Guid pilotUserId, MissionSessionRequest request, CancellationToken ct = default)` | Validates the request, persists a `class='mission'` row in `sessions`, mints an ES256 access token bound to that session id, returns the token + expiry + session id. |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
- **Validation**:
|
||||
- `mission_id` must match `^M-\d{4}-\d{2}-\d{2}-\d{3}$` (compiled regex).
|
||||
- `planned_duration_h` ∈ `[0.1, 12.0]` — anything outside throws `BusinessException(InvalidMissionRequest)` (HTTP 400).
|
||||
- `aircraft_id` must exist in `users` with `Role=CompanionPC`; otherwise `BusinessException(AircraftNotFound)`.
|
||||
- **Session row first, then token** — the row is inserted *before* the JWT is minted so revocation lookups can never miss a token already in the wild.
|
||||
- **Lifetime** = `planned_duration_h + 1.0` (the 1-hour buffer covers post-flight reconnect grace).
|
||||
- **Family handling** — mission sessions are their own family (`family_id = id`); they never rotate.
|
||||
- **Token claims**: `sub` = pilotUserId, `sid` = session id, `jti` = unique token id, `mission_id`, `aircraft_id`, `token_class="mission"`, optional `permissions` (multi-valued), optional `valid_region` (JSON-typed claim). Audience pinned to `satellite-provider`.
|
||||
- **Auto-revoke on reconnect** is implemented in `Program.cs` via `ISessionService.RevokeMissionsForAircraft`, fired from `/login` and `/token/refresh` whenever the caller is a `CompanionPC` user.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `IDbFactory` — admin connection for inserting the mission session row, read connection for the aircraft existence check
|
||||
- `IJwtSigningKeyProvider` — ES256 active key
|
||||
- `IOptions<JwtConfig>` — issuer
|
||||
- `Session` entity, `SessionClasses.Mission` constant
|
||||
- `MissionSessionRequest` / `MissionSessionResponse` DTOs
|
||||
|
||||
## Consumers
|
||||
|
||||
- `Program.cs` `/sessions/mission` (requires interactive auth; per AZ-533 the AC-6 step-up MFA gate is a TODO until org-wide MFA adoption)
|
||||
|
||||
## Data Models
|
||||
|
||||
Operates on the `Session` entity (`class='mission'`, `aircraft_id` set, `refresh_hash` null).
|
||||
|
||||
## Configuration
|
||||
|
||||
- `JwtConfig.Issuer` — issuer claim of the minted token.
|
||||
- Hard-coded constants:
|
||||
- `MissionAudience = "satellite-provider"` — verifier-side audience gate.
|
||||
- `MaxDurationHours = 12.0`, `MinDurationHours = 0.1`.
|
||||
- `LifetimeBufferHours = 1.0`.
|
||||
|
||||
## External Integrations
|
||||
|
||||
PostgreSQL via `IDbFactory`. Token consumed by the `satellite-provider` workspace (verifier-side enforcement of `mission_id`/`aircraft_id`/`valid_region` is filed under that workspace).
|
||||
|
||||
## Security
|
||||
|
||||
- Long-lived tokens are inherently dangerous if leaked. Hardware binding (mTLS / DPoP / `cnf`) is the long-term answer; documented as a known risk in `_docs/05_security/security_report.md`.
|
||||
- The narrowed audience (`satellite-provider`) prevents a stolen mission token from being usable against the admin API itself; admin endpoints still require `JwtConfig.Audience`.
|
||||
- The `valid_region` bbox is informational until `satellite-provider` enforces it (cross-workspace coordination ticket).
|
||||
- Mission tokens are auto-revoked the moment the aircraft reconnects (`/login` or `/token/refresh` from a `CompanionPC` user). Verifiers polling `/sessions/revoked` see the revocation within their poll interval (≤ 30 s).
|
||||
|
||||
## Tests
|
||||
|
||||
- `e2e/Azaion.E2E/Tests/MissionTokenTests.cs` — AC-1 (correct lifetime + claims), AC-2 (12h cap), AC-3 (scope claims), AC-4 (auto-revoke on reconnect), AC-5 (auth required).
|
||||
Reference in New Issue
Block a user