Files
admin/_docs/02_document/modules/services_mission_token_service.md
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

4.2 KiB

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).