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

10 KiB

Module: Azaion.AdminApi.Program

Purpose

Application entry point: configures DI, middleware, authentication, authorization, CORS, Swagger, logging, and defines all HTTP endpoints using ASP.NET Core Minimal API.

Public Interface (HTTP Endpoints)

Cycle 1 (2026-05-13) note — endpoint surface changed by AZ-513 (detection-class CRUD), AZ-196 (device auto-registration), AZ-197 (hardware-binding removal). AZ-183 (OTA update check + publish) was reverted later the same day after the security audit (finding F-1).

Cycle 2 (2026-05-14) note A — three resource endpoints removed as obsolete: POST /resources/get/{dataFolder?}, GET /resources/get-installer, GET /resources/get-installer/stage. The encrypted-download support stack went with them. ADR-003 in architecture.md was retired.

Cycle 2 (2026-05-14) note B (auth modernization) — eight endpoints added or replaced as part of Epic AZ-529 (Auth Modernization) + AZ-530 (CMMC Hardening). The /login shape is now dual-token (access + refresh) when MFA is off, or MfaRequiredResponse when MFA is enabled. CORS dropped the cleartext origin (AZ-538). HSTS + HTTPS redirection are wired in non-Development environments. Per-IP sliding-window rate limit added to /login (and /login/mfa). Public-key JWKS feed live at /.well-known/jwks.json (AZ-532).

Method Path Auth Summary Cycle origin
GET /health/live Anonymous Liveness check (Cache-Control: no-store); excluded from Swagger AZ-510
GET /health/ready Anonymous Readiness check — pings both DB connections with a 2-s timeout; 503 with reason on failure AZ-510
POST /login Anonymous + per-IP rate limit Validates credentials. Returns LoginResponse (access + refresh) when MFA is off, MfaRequiredResponse when MFA is enabled. AZ-531 / AZ-534 / AZ-537
POST /login/mfa Anonymous + per-IP rate limit Second-factor verification (TOTP or recovery code). Returns LoginResponse. AZ-534
POST /token/refresh Anonymous (token in body) Rotates a refresh token; returns a fresh LoginResponse. Reuse-detection kills the family. AZ-531
POST /logout Authenticated Revokes the caller's current session (idempotent — returns { alreadyRevoked }). AZ-535
POST /logout/all Authenticated Revokes every active session for the caller's user (returns { revoked: N }). AZ-535
POST /sessions/{sid:guid}/revoke ApiAdmin Admin revoke-by-session-id. AZ-535
GET /sessions/revoked revocationReader (Service or ApiAdmin) Verifier-poll snapshot of revoked-but-not-yet-expired sessions. Cache-Control: no-cache; since clamped to now - 12h. AZ-535
POST /sessions/mission Authenticated Mints a long-lived no-refresh mission token bound to one aircraft. AZ-533 AC-6 step-up MFA gate is a TODO comment until org-wide MFA adoption. AZ-533
POST /users/me/mfa/enroll Authenticated Returns TOTP secret + otpauth URL + QR PNG + 10 recovery codes (ONCE). AZ-534
POST /users/me/mfa/confirm Authenticated Validates one TOTP code and flips mfa_enabled=true. AZ-534
POST /users/me/mfa/disable Authenticated Removes MFA (requires password + valid code). AZ-534
GET /.well-known/jwks.json Anonymous (excluded from Swagger) Public JWKS feed for verifiers; Cache-Control: public, max-age=3600. AZ-532
POST /users ApiAdmin Creates a new user.
POST /devices ApiAdmin Creates a CompanionPC device user (auto serial / email / 32-hex password). AZ-196
GET /users/current Any authenticated Returns current user from JWT claims.
GET /users ApiAdmin Lists users with optional email/role filters.
PUT /users/queue-offsets/set Any authenticated Updates user's queue offsets.
PUT /users/{email}/set-role/{role} ApiAdmin Changes a user's role.
PUT /users/{email}/enable ApiAdmin Enables a user account.
PUT /users/{email}/disable ApiAdmin Disables a user account.
DELETE /users/{email} ApiAdmin Removes a user.
POST /resources/{dataFolder?} Any authenticated Uploads a resource file.
GET /resources/list/{dataFolder?} Any authenticated Lists files in a resource folder.
POST /resources/clear/{dataFolder?} ApiAdmin Clears a resource folder.
POST /classes ApiAdmin Creates a detection class. AZ-513
PATCH /classes/{id:int} ApiAdmin Updates a detection class (partial-merge). AZ-513
DELETE /classes/{id:int} ApiAdmin Deletes a detection class. AZ-513

Removed endpoints

The following endpoints have been removed and now return 404:

Method Path Removed in Reason
PUT /users/hardware/set cycle 1 (AZ-197) hardware-binding feature deleted
POST /resources/check cycle 1 (AZ-197) hardware-binding side-effect probe
POST /get-update post-cycle-1 (AZ-183 reverted) security audit F-1
POST /resources/publish post-cycle-1 (AZ-183 reverted) OTA flow obsolete
POST /resources/get/{dataFolder?} cycle 2 obsolete; ADR-003 retired
GET /resources/get-installer cycle 2 installer-shipping era over
GET /resources/get-installer/stage cycle 2 same as above

Internal Logic

DI Registration

  • IJwtSigningKeyProviderJwtSigningKeyProvider (Singleton; eagerly built before app.Build() so JwtBearer and DI share one instance) — AZ-532
  • IUserServiceUserService (Scoped)
  • IAuthServiceAuthService (Scoped)
  • IRefreshTokenServiceRefreshTokenService (Scoped) — AZ-531
  • ISessionServiceSessionService (Scoped) — AZ-535
  • IMissionTokenServiceMissionTokenService (Scoped) — AZ-533
  • IMfaServiceMfaService (Scoped) — AZ-534
  • IResourcesServiceResourcesService (Scoped)
  • IDetectionClassServiceDetectionClassService (Scoped)
  • IAuditLogAuditLog (Scoped) — AZ-537 / AZ-534
  • IDbFactoryDbFactory (Singleton)
  • ICacheMemoryCache (Scoped)
  • LazyCache via AddLazyCache()
  • ASP.NET Core DataProtectionSetApplicationName("Azaion.AdminApi"); if DataProtection:KeysFolder is set, persisted to filesystem (production requirement for MFA-secret durability) — AZ-534
  • FluentValidation validators auto-discovered from RegisterUserValidator assembly
  • BusinessExceptionHandler registered as exception handler

Middleware Pipeline

  1. Swagger (Development only)
  2. HSTS + HTTPS redirection (non-Development only) — AZ-538
  3. CORS (AdminCorsPolicy)
  4. Authentication (JWT Bearer with ValidAlgorithms = [ES256] and an IssuerSigningKeyResolver that picks by kid from IJwtSigningKeyProvider.All)
  5. Authorization
  6. Rate limiter (UseRateLimiter) — AZ-537
  7. URL rewrite: root //swagger
  8. Exception handler

Authorization Policies

  • apiAdminPolicy: requires RoleEnum.ApiAdmin role
  • revocationReaderPolicy: requires RoleEnum.Service OR RoleEnum.ApiAdmin (gates /sessions/revoked) — AZ-535

Rate Limit Policies

  • LoginPerIpPolicy = "login-per-ip" — sliding-window limiter keyed on RemoteIpAddress. Configured from AuthConfig.RateLimit.PerIpPermitLimit / PerIpWindowSeconds. On rejection, sets Retry-After from the RetryAfter lease metadata. Applied to /login and /login/mfa.

Configuration Sections

  • JwtConfig — JWT signing/validation (Issuer, Audience, KeysFolder, ActiveKid, AccessTokenLifetimeMinutes)
  • SessionConfig — refresh-token sliding/absolute window (RefreshSlidingHours, RefreshAbsoluteHours) — AZ-531
  • AuthConfig — rate-limit and lockout knobs — AZ-537
  • ConnectionStrings — DB connections
  • ResourcesConfig — file storage path

Kestrel

  • Max request body size: 200 MB

Logging

  • Serilog: console + rolling file (logs/log.txt)

CORS

  • Allowed origin: https://admin.azaion.com (the cleartext http:// origin was dropped by AZ-538)
  • All methods and headers allowed; credentials allowed

Helpers

Local static helpers used by logout / mission endpoints:

  • ParseSidClaim(ClaimsPrincipal) — extracts the sid claim; throws InvalidRefreshToken (401) if missing/malformed.
  • ParseUserIdClaim(ClaimsPrincipal) — extracts NameIdentifier; same error semantics.
  • IssueDualTokens(...) — shared by /login and /login/mfa; calls IRefreshTokenService.IssueForNewLogin, IAuthService.CreateToken, plus ISessionService.RevokeMissionsForAircraft when the caller is RoleEnum.CompanionPC (AZ-533 AC-4 reconnect trigger).

Dependencies

All services, configs, entities, and request types from Azaion.Common and Azaion.Services. New dependencies wired in cycle 2: Microsoft.AspNetCore.RateLimiting, Microsoft.AspNetCore.DataProtection.

Consumers

None — application entry point.

Data Models

None defined here.

Configuration

Reads JwtConfig, SessionConfig, AuthConfig, ConnectionStrings, ResourcesConfig from IConfiguration. Optional DataProtection:KeysFolder for MFA-secret durability.

External Integrations

  • PostgreSQL (via DI-registered DbFactory)
  • Local filesystem (via ResourcesService and JwtSigningKeyProvider for PEM keys)

Security

  • JWT Bearer with full validation: ValidateIssuer, ValidateAudience, ValidateLifetime, ValidateIssuerSigningKey, ValidAlgorithms = [ES256] (AZ-532 AC-5).
  • Issuer signing keys resolved per-kid via IJwtSigningKeyProvider; supports rotation overlap.
  • Public JWKS endpoint exposes only public components (x/y for EC); Cache-Control: public, max-age=3600.
  • Per-IP sliding-window rate limit on /login and /login/mfa (AZ-537).
  • HSTS (1 year, includeSubDomains, preload) + HTTPS redirect in non-Development envs (AZ-538).
  • CORS restricted to HTTPS origin only (AZ-538).
  • DataProtection key folder must be a persistent volume in Production so encrypted MFA secrets survive restarts (AZ-534 known operational requirement; carry-forward F3 asks for a startup warning when running in Production with the folder unset).
  • Role-based authorization for admin endpoints; new Service role gates the verifier-poll feed.

Tests

None directly; tested through e2e/Azaion.E2E/Tests/ (Login, RefreshToken, RateLimitLockout, Logout, Jwks, MissionToken, MfaEnrollment, MfaLogin, PasswordHashing).