Files
admin/_docs/02_document/modules/common_entities_session.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.3 KiB

Module: Azaion.Common.Entities.Session

Purpose

Domain entity representing one issued refresh token (interactive sessions) or one mission token (long-lived UAV sessions). One row per issued token; rotated rows chain via ParentSessionId and share a FamilyId so reuse-detection and family-wide revocation can key off it.

Added in cycle 2 (2026-05-14). Initial shape from AZ-531 (interactive refresh-token sessions); extended in the same cycle by AZ-535 (RevokedByUserId), AZ-533 (Class, AircraftId), and AZ-534 (MfaAuthenticated).

Public Interface

Session

Property Type Description
Id Guid Primary key.
UserId Guid FK to users.id.
RefreshHash string? SHA-256 hex of the opaque refresh token. NULL for mission sessions (they have no refresh value). Unique-indexed.
FamilyId Guid All rotations of the same login share this id. For interactive root rows and for mission rows, FamilyId == Id.
IssuedAt DateTime Row creation time.
LastUsedAt DateTime Updated on rotation; informational.
ExpiresAt DateTime Sliding (interactive) or absolute (mission) expiry.
RevokedAt DateTime? Set on rotation, reuse-detection, logout, admin revoke, post-flight reconnect.
RevokedReason string? One of SessionRevokedReasons.
ParentSessionId Guid? The previous row in the family (set on rotation).
FamilyStartedAt DateTime First-issue time of the family — used for the absolute expiry check.
RevokedByUserId Guid? AZ-535 — audit trail of who revoked the session. NULL for system revocations (rotation, reuse, post-flight).
Class string AZ-533 — "interactive" (default) or "mission".
AircraftId Guid? AZ-533 — for mission sessions, the CompanionPC user the mission token belongs to. Used by RevokeMissionsForAircraft.
MfaAuthenticated bool AZ-534 — pinned at issue; refresh rotation inherits the original AMR strength even if MFA is enabled/disabled mid-session.

SessionRevokedReasons (constants)

Value When
rotated Old row marked as superseded by a successful refresh rotation.
reuse_detected OAuth 2.1 §6.1 — already-rotated refresh re-presented; whole family killed.
logged_out User called POST /logout.
logged_out_all User called POST /logout/all.
admin_revoked Admin called POST /sessions/{sid}/revoke.
post_flight_reconnect Aircraft reconnected; mission auto-revoked.
family_revoked Reserved (manual family-wide revocation; not currently emitted).

SessionClasses (constants)

Value Meaning
interactive Refresh-backed user session (AZ-531 default).
mission Long-lived no-refresh UAV mission token (AZ-533).

Internal Logic

None — pure data class. All session lifecycle logic lives in RefreshTokenService, SessionService, MissionTokenService.

Dependencies

None.

Consumers

  • RefreshTokenService — inserts root/family rows, updates on rotation/reuse-detection
  • SessionService — revocation paths and the verifier-poll snapshot
  • MissionTokenService — inserts mission-class rows
  • AzaionDb.SessionsITable<Session> access
  • AzaionDbSchemaHolder — maps Session to the sessions table

Data Models

Maps to PostgreSQL table sessions (defined in env/db/08_sessions.sql, extended by 09_sessions_logout_and_mission.sql and 10_users_mfa.sql).

Configuration

None.

External Integrations

None.

Security

  • refresh_hash stores SHA-256 of the opaque token; the plaintext is never persisted.
  • The family_id partial index sessions_family_active_idx WHERE revoked_at IS NULL keeps reuse-detection and RevokeAllForUser cheap even as the revoked tail grows.
  • Auto-revoke-on-reconnect (RevokeMissionsForAircraft) closes the mission-token "lost UAV" risk when the aircraft phones home again; the partial index sessions_aircraft_active_idx (aircraft_id, class) WHERE revoked_at IS NULL AND aircraft_id IS NOT NULL keeps that check O(active mission rows).

Tests

Indirectly tested via RefreshTokenTests, LogoutTests, MissionTokenTests, and MfaLoginTests (which all exercise the entity through the service layer).