docs+src: complete Steps 1-3 outcomes + auth re-sync baseline

This commit captures everything produced during autodev existing-code
Steps 1 (Document), 2 (Architecture Baseline Scan), and 3 (Test Spec),
together with the targeted auth + CORS re-sync triggered on 2026-05-14
when codebase drift was detected at Step 4 entry. None of this work was
previously committed.

Step 1 (Document) — 50+ _docs/02_document/ files: problem, solution,
architecture, system flows, glossary, module-layout, per-component
specs (01..06), modules, deployment, diagrams, data model, FINAL
report, verification log, discovery.

Step 2 (Architecture Baseline) — architecture_compliance_baseline.md.
Verdict PASS_WITH_WARNINGS (0 Critical, 0 High, 1 Medium, 2 Low). No
High/Critical findings; auto-chained to Step 3 per existing-code flow.

Step 3 (Test Spec) — _docs/02_document/tests/* (67 scenarios across
blackbox, security, resilience, resource-limit, performance), plus
e2e/docker-compose.test.yml, e2e/seed/run.sh, scripts/run-tests.sh,
scripts/run-performance-tests.sh. Coverage 88% over the active scope
(40 of 45 items covered, 6 RB-deferred, 5 documented-as-uncovered).

Targeted auth + CORS re-sync — replaces the deleted in-house token
issuer with a JWKS-verifier model. AuthController and TokenService
removed; JwtExtensions switched from HS256 symmetric to ES256 over
admin's JWKS. ConfigurationResolver and CorsConfigurationValidator
added under src/Infrastructure/. ADR-002 and ADR-006 retired; SEC-01,
SEC-02, SEC-03 marked Closed. One new testability risk recorded in
architecture.md Open Risks Section 6 (JWKS HTTPS gating).

Source changes:
- src/Auth/JwtExtensions.cs (modified) — ES256, JWKS, alg pinning
- src/Program.cs (modified) — DI wiring for ConfigurationResolver
  and CorsConfigurationValidator
- src/Controllers/AuthController.cs (deleted) — no in-service issuance
- src/Services/TokenService.cs (deleted) — same
- src/Infrastructure/ConfigurationResolver.cs (new)
- src/Infrastructure/CorsConfigurationValidator.cs (new)
- .env.example (new) — required env var documentation
- .gitignore (updated)

Cross-repo coordination: _docs/cross-repo/flights_h1_h2_h3_change_spec
captures the change-spec for downstream services that consumed the now
deleted /auth endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 20:19:05 +03:00
parent 08eadc1158
commit 03f879206e
66 changed files with 6006 additions and 133 deletions
@@ -0,0 +1,107 @@
# Flights — parallel H1/H2/H3 change spec
Drop-in equivalent of the H1/H2/H3 fixes landed in `annotations/` this cycle. The
workspace boundary rule (`.cursor/rules/workspace-boundary.mdc`) prevents this
agent from editing the `flights/` repo directly; this document is the contract
the flights workspace should implement on its own branch.
Source of truth for the new files / patterns is the annotations workspace:
- `annotations/src/Auth/JwtExtensions.cs` — JWKS verifier wiring.
- `annotations/src/Infrastructure/ConfigurationResolver.cs` — fail-fast env-var helper.
- `annotations/src/Infrastructure/CorsConfigurationValidator.cs` — CORS allow-list guard.
- `annotations/src/Program.cs` — composition root.
The flights changes are byte-equivalent except for the items called out under
"Differences" below.
## H1 — JWKS verifier (replace HS256 shared secret)
In `flights/Auth/JwtExtensions.cs`:
- Replace `AddJwtAuth(string jwtSecret)` with `AddJwtAuth(IConfiguration configuration)`.
- Resolve `JWT_ISSUER`, `JWT_AUDIENCE`, `JWT_JWKS_URL` via the new
`ConfigurationResolver.ResolveRequiredOrThrow` helper (no fallbacks).
- Build a `ConfigurationManager<JsonWebKeySet>` over a minimal
`IConfigurationRetriever<JsonWebKeySet>` (admin only exposes JWKS, not the full
OIDC discovery doc — copy the `JwksRetriever` private class verbatim from
annotations).
- `TokenValidationParameters` must be:
- `ValidateIssuer = true`, `ValidIssuer = issuer`
- `ValidateAudience = true`, `ValidAudience = audience`
- `ValidateLifetime = true`, `ValidateIssuerSigningKey = true`
- `ValidAlgorithms = [SecurityAlgorithms.EcdsaSha256]` (pinned)
- `RequireSignedTokens = true`, `RequireExpirationTime = true`
- `ClockSkew = TimeSpan.FromSeconds(30)`
- `IssuerSigningKeyResolver` returns `jwks.GetSigningKeys()` filtered by `kid`.
- Keep the existing authorization policies in place (`FL`, `GPS`).
## H2 — fail-fast env vars (drop insecure defaults)
In `flights/Program.cs`:
- Delete `?? "Host=localhost;Database=azaion;Username=postgres;Password=changeme"` for `DATABASE_URL` and resolve it through `ConfigurationResolver.ResolveRequiredOrThrow`.
- Delete `?? "development-secret-key-min-32-chars!!"` for `JWT_SECRET` and remove the variable entirely (`AddJwtAuth` now takes `IConfiguration`).
## H3 — config-driven CORS allow-list
In `flights/Program.cs`:
- Read `CorsConfig:AllowedOrigins` (string array) and `CorsConfig:AllowAnyOrigin` (bool).
- Call `CorsConfigurationValidator.EnsureSafeForEnvironment(...)` before `AddCors`. In `Production` with empty origins and `AllowAnyOrigin=false`, throw.
- Build the default policy with `WithOrigins(allowedOrigins)` (locked) or `AllowAnyOrigin()` (permissive opt-in) per `ShouldUsePermissivePolicy`.
- After `builder.Build()`, log a warning when running with the permissive default in a non-Production environment (`ShouldWarnAboutPermissiveDefault`).
Copy `CorsConfigurationValidator.cs` verbatim, only changing the namespace to
`Azaion.Flights.Infrastructure`.
## Side-effect: local token minting
If flights has its own `Services/TokenService.cs` or `Controllers/AuthController.cs`
that mints tokens with HS256 (matching the pattern annotations had before this
cycle), it MUST be removed; otherwise the new validator (`ValidAlgorithms` pinned
to `EcdsaSha256`) will reject the locally-minted tokens at the next
`[Authorize]` hop. Admin is the sole token issuer for the suite after this
change.
If flights had no local token minting before, this section does not apply.
## Differences from annotations
- Authorization policies in `JwtExtensions`: keep flights' existing `FL` and
`GPS` policies; do NOT add annotations' `ANN`/`DATASET`/`ADM` policies.
- Namespace prefix: `Azaion.Flights` instead of `Azaion.Annotations`.
## `.env.example` (new file)
Mirror annotations' template; required keys:
```
DATABASE_URL=
JWT_ISSUER=AzaionApi
JWT_AUDIENCE=Annotators/OrangePi/Admins
JWT_JWKS_URL=https://admin.azaion.com/.well-known/jwks.json
# CorsConfig__AllowedOrigins__0=https://...
# CorsConfig__AllowAnyOrigin=false
```
Confirm the `Issuer` / `Audience` values against the production admin
deployment before merging.
## Docs to update in `flights/_docs/`
- `02_document/modules/auth-identity.md` (or equivalent) — verifier-only role,
remove any HS256 references, document the JWKS resolver wiring.
- `02_document/deployment/environment_strategy.md` (or equivalent) —
required-vs-optional env table; remove `JWT_SECRET`, add the three new JWT
vars and the CORS config keys.
- `02_document/architecture.md` (or equivalent) — retire any ADRs that pinned
HS256 / wide-open CORS.
## Verification before merge
1. `dotnet build` succeeds.
2. Manually unset `JWT_ISSUER` (or `JWT_AUDIENCE`, `JWT_JWKS_URL`, `DATABASE_URL`) and confirm startup throws `InvalidOperationException` with a helpful message naming the env var.
3. With `ASPNETCORE_ENVIRONMENT=Production` and no `CorsConfig:AllowedOrigins`, confirm startup throws.
4. With a valid admin-issued ES256 token, confirm `[Authorize]` endpoints return 200.
5. With a token forged using `alg=HS256` and admin's public key as the HMAC secret, confirm the endpoint returns 401 (alg-confusion attack rejected).