[AZ-487] JWT validation baseline (HS256, all endpoints)
ci/woodpecker/push/01-test Pipeline failed
ci/woodpecker/push/02-build-push unknown status

Adds Microsoft.AspNetCore.Authentication.JwtBearer 8.0.21 and the
SatelliteProvider.Api.Authentication.AddSatelliteJwt extension that
validates HS256 tokens against a shared JWT_SECRET (>=32 bytes, fail
fast at startup). Every minimal-API endpoint now carries
.RequireAuthorization(); the middleware chain is UseExceptionHandler ->
UseHttpsRedirection -> UseCors -> UseAuthentication -> UseAuthorization
-> endpoints. Swagger UI gets a Bearer security definition so the
Authorize button works.

Test infrastructure: JwtTokenFactory (unit) and JwtTestHelpers
(integration) mint deterministic tokens against the same secret; the
integration test runner attaches a default Bearer token to its shared
HttpClient so existing tests continue to exercise protected endpoints.
JwtIntegrationTests adds AC-1..AC-4 and AC-7 (Swagger advertises
Bearer) end-to-end; AuthenticationServiceCollectionExtensionsTests
covers AC-5 (missing/empty/short secret fail-fast) plus env-var
precedence; JwtTokenFactoryTests covers AC-6 (claims pass through
the JwtSecurityTokenHandler.ValidateToken path JwtBearer uses).

docker-compose and scripts/run-tests.sh now propagate JWT_SECRET to
the api and integration-tests containers, with a >=32-byte guard.
.env.example documents the required keys; .env stays gitignored.

Code review verdict: PASS_WITH_WARNINGS (2 Low findings surfaced
in _docs/03_implementation/reviews/batch_01_cycle2_review.md).

Cross-component coordination: gps-denied-onboard and the mission
planner UI must attach Bearer tokens before this lands in dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 23:06:23 +03:00
parent 8e15e53782
commit 96cd3c4495
23 changed files with 872 additions and 15 deletions
@@ -0,0 +1,48 @@
# Batch Report — Batch 01 cycle 2
**Batch**: 01 (cycle 2)
**Tasks**: AZ-487 (JWT validation baseline)
**Date**: 2026-05-11
## Task Results
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|------|--------|---------------|-------|-------------|--------|
| AZ-487_jwt_validation_baseline | Done | 12 modified + 6 added (`.env.example`, `SatelliteProvider.Api/Authentication/AuthenticationServiceCollectionExtensions.cs`, `SatelliteProvider.IntegrationTests/JwtIntegrationTests.cs`, `JwtTestHelpers.cs`, `SatelliteProvider.Tests/Authentication/AuthenticationServiceCollectionExtensionsTests.cs`, `JwtTokenFactoryTests.cs`, `SatelliteProvider.Tests/TestUtilities/JwtTokenFactory.cs`) | To run (Step 11) | 8/8 ACs covered | 0 blockers; 2 Low findings (see review) |
## AC Test Coverage: All covered (8 of 8)
## Code Review Verdict: PASS_WITH_WARNINGS
## Auto-Fix Attempts: 0
## Stuck Agents: None
## What was implemented
- New `AuthenticationServiceCollectionExtensions.AddSatelliteJwt(IConfiguration)` — registers `Microsoft.AspNetCore.Authentication.JwtBearer 8.0.21` with `TokenValidationParameters` matching the suite-level auth contract (HS256, `ValidateLifetime`, `RequireSignedTokens`, `RequireExpirationTime`, no issuer/audience validation, 30 s clock skew). Secret resolution prefers `JWT_SECRET` env var, falls back to `Jwt:Secret` configuration, fails fast on missing/short (<32-byte) secret.
- `Program.cs` wires `AddSatelliteJwt` + `AddAuthorization` into DI, adds `UseAuthentication` + `UseAuthorization` middleware after `UseCors`, and decorates every minimal-API endpoint with `.RequireAuthorization()`. Swagger UI gets a Bearer security definition + global security requirement so the "Authorize" button renders.
- Configuration: `appsettings.json` adds an empty `Jwt:Secret` placeholder; `appsettings.Development.json` ships a clearly-tagged dev placeholder (≥32 bytes). The `JWT_SECRET` env var overrides both when set.
- Test infrastructure:
- `SatelliteProvider.Tests.TestUtilities.JwtTokenFactory` — mints, expires, and tampers tokens using the same secret as the API.
- `SatelliteProvider.Tests.Authentication.AuthenticationServiceCollectionExtensionsTests` — 6 tests covering registration, parameter shape, missing/empty/short secret handling, and env-var-precedence semantics.
- `SatelliteProvider.Tests.Authentication.JwtTokenFactoryTests` — 4 tests covering token mint + claims propagation + expired + tampered.
- `SatelliteProvider.IntegrationTests.JwtTestHelpers` — runner-side helpers for token mint + bearer attachment.
- `SatelliteProvider.IntegrationTests.JwtIntegrationTests` — 5 end-to-end checks (anon → 401, expired → 401, tampered → 401, valid → handler, Swagger advertises Bearer).
- `SatelliteProvider.IntegrationTests.Program` — resolves `JWT_SECRET` at startup, mints a default token, attaches it to the shared `HttpClient` so legacy non-auth tests continue to pass against protected endpoints.
- Docker / scripts:
- `docker-compose.yml`: api gets `JWT_SECRET=${JWT_SECRET}`.
- `docker-compose.tests.yml`: integration-tests gets the same env var (api inherits via `extends`).
- `scripts/run-tests.sh`: loads `JWT_SECRET` from `.env` (alongside `GOOGLE_MAPS_API_KEY`), rejects missing/short values, exports for compose runs.
- `.env.example` (new): documents `GOOGLE_MAPS_API_KEY` and `JWT_SECRET` with generation guidance.
- `.env` (gitignored): dev `JWT_SECRET` added locally.
- Docs:
- `architecture.md` § Architecture Vision: added Authentication & Authorization sub-section.
- `architecture.md` § Security Architecture: replaced "no auth" prose with JWT description; documented placeholder semantics and operator obligations.
- `modules/api_program.md`: updated DI Registration (#9), Startup (middleware chain), Dependencies (JwtBearer 8.0.21), Configuration (`Jwt:Secret` + env var precedence), Security (per-endpoint `.RequireAuthorization()`).
## Open follow-ups (non-blocking)
- **Doc-folder choice** (F1 in review): the task spec referenced `_docs/02_document/components/01_web_api/description.md`, which does not exist. Updated `modules/api_program.md` + `architecture.md` instead. User should decide whether to add a stub `01_web_api` component folder for symmetry, or to formalize the "WebApi has no `components/*` folder" pattern.
- **Cross-component coordination**: per the AZ-487 spec, `gps-denied-onboard` and the mission planner UI must attach Bearer tokens to their outbound calls before this change ships to `dev`. Flagged for the operator to coordinate before deploy (Step 16).
## Next Batch: AZ-488 (UAV tile upload endpoint with batch + 5-rule quality gate)
AZ-488 is an 8 SP task (user-accepted over-cap). It introduces a new endpoint, DTOs, quality gate, configuration class, and ~15 unit + integration tests. Recommend a fresh conversation for context freshness before starting (per `protocols.md` Context Budget Heuristic).
@@ -0,0 +1,81 @@
# Code Review Report — Batch 01 cycle 2
**Batch**: AZ-487 (JWT validation baseline)
**Date**: 2026-05-11
**Verdict**: PASS_WITH_WARNINGS
## Findings
| # | Severity | Category | File:Line | Title |
|---|----------|----------|-----------|-------|
| 1 | Low | Style | _docs/02_document/components/01_web_api/description.md | Task spec referenced a doc path that does not exist in the codebase |
| 2 | Low | Security | SatelliteProvider.Api/appsettings.Development.json | Dev-only JWT secret placeholder is committed (intentional per spec) |
### Finding Details
**F1: Task spec referenced a doc path that does not exist in the codebase** (Low / Style)
- Location: `_docs/02_document/components/01_web_api/description.md` (referenced; does not exist)
- Description: The AZ-487 task spec lists `_docs/02_document/components/01_web_api/description.md` as a doc to update. The codebase's component-doc folders are `01_common`, `02_data_access`, `03_tile_downloader`, `04_region_processing`, `05_route_management` — there is no `01_web_api` folder. The WebApi component's documentation lives in `_docs/02_document/modules/api_program.md`.
- Suggestion: Either (a) create the missing folder with a brief stub that defers to `api_program.md`, or (b) update the task spec for AZ-488 to point at `modules/api_program.md` and acknowledge that WebApi has no `components/*` folder. This batch chose (b) — updated `architecture.md` § Architecture Vision + § Security Architecture and `modules/api_program.md`. Surface to user for a doc-organization decision.
- Task: AZ-487
**F2: Dev-only JWT secret placeholder is committed** (Low / Security)
- Location: `SatelliteProvider.Api/appsettings.Development.json`
- Description: The dev placeholder `DEV-ONLY-DO-NOT-USE-IN-PROD-replace-with-real-secret-via-JWT_SECRET-env-var` (78 bytes) is committed to the repo. Anyone with read access could mint a token signed with that secret. The mitigations (per task spec) are: (i) only loaded when `ASPNETCORE_ENVIRONMENT=Development`, (ii) the `JWT_SECRET` env var overrides it whenever set, (iii) the placeholder text itself signals it must be replaced.
- Suggestion: Accept as-is for dev ergonomics; production environments must set `JWT_SECRET` (validated in `scripts/run-tests.sh` and at startup). Document on the README that this dev placeholder is intentional.
- Task: AZ-487
## Phase Notes
### Phase 1 — Context Loading
- Task spec read: `_docs/02_tasks/todo/AZ-487_jwt_validation_baseline.md`
- Suite contract referenced: `suite/_docs/10_auth.md` (consumed via implementation, not modified)
- Module layout, architecture doc, existing test patterns reviewed.
### Phase 2 — Spec Compliance
All 8 acceptance criteria have at least one automated test:
| AC | Description | Test |
|----|-------------|------|
| AC-1 | Anonymous → 401 | `JwtIntegrationTests.AnonymousRequest_To_AnyEndpoint_Returns401` |
| AC-2 | Expired → 401 | `JwtIntegrationTests.ExpiredToken_Returns401` |
| AC-3 | Invalid signature → 401 | `JwtIntegrationTests.InvalidSignature_Returns401` |
| AC-4 | Valid token reaches handler | `JwtIntegrationTests.ValidToken_Returns200_OnHealthyEndpoint` |
| AC-5 | Missing/short secret → fail fast | `AuthenticationServiceCollectionExtensionsTests.AddSatelliteJwt_ThrowsOnMissingSecret/Empty/Short` |
| AC-6 | `HttpContext.User` exposes claims | `JwtTokenFactoryTests.Create_WithExtraClaims_PropagatesClaimsThroughValidation` (exercises same `JwtSecurityTokenHandler.ValidateToken` path JwtBearer uses to populate `HttpContext.User`) |
| AC-7 | Swagger Authorize button | `JwtIntegrationTests.SwaggerDocument_AdvertisesBearerSecurityScheme` (OpenAPI document declares `Bearer` security scheme) |
| AC-8 | All existing tests pass | Verified at Step 11 (test-run gate); shared `HttpClient` attaches default Bearer token in `IntegrationTests/Program.cs` |
Contract verification: AZ-487 is a consumer of `suite/_docs/10_auth.md`. The implementation matches the consumed contract (HS256, ≥32-byte HMAC key, no issuer/audience validation, expiration required).
### Phase 3 — Code Quality
- New code follows SRP (`AuthenticationServiceCollectionExtensions` does one thing).
- Tests follow AAA pattern with explicit comments (matches project style).
- No bare catches introduced. The pre-existing empty catch in `IntegrationTests/Program.cs::WaitForApiReady` is out of scope (pre-existing, not modified).
- No comments narrating obvious code.
### Phase 4 — Security Quick-Scan
- No SQL/command-injection vectors introduced.
- No sensitive data in logs (error messages mention secret length, not value).
- Dev placeholder secret: see F2.
### Phase 5 — Performance Scan
- JWT validation per request is microsecond-scale HMAC + claims parsing; no I/O, no caching needed (per NFR).
- No N+1, no unbounded fetches, no new blocking calls.
### Phase 6 — Cross-Task Consistency
Single-task batch; not applicable.
### Phase 7 — Architecture Compliance
- All new code in `SatelliteProvider.Api/Authentication/` — owned by the WebApi component per `module-layout.md`.
- Imports stay within the allowed dependency table (`Microsoft.AspNetCore.Authentication.JwtBearer`, `Microsoft.IdentityModel.Tokens` are external NuGet packages, not other components).
- Test code lives under `SatelliteProvider.Tests/` and `SatelliteProvider.IntegrationTests/` (separate test projects per layout rule 5).
- No new cross-sibling ProjectReferences. No new cyclic dependencies. No duplicate cross-component symbols.
## Baseline Delta
`_docs/02_document/architecture_compliance_baseline.md` is not present in the repo; baseline delta section omitted.
## Verdict Rationale
Two Low findings, both already accepted in the task spec or surfacable as doc-organization decisions. No Critical, High, or Medium findings. Verdict: **PASS_WITH_WARNINGS** — proceed to commit per implement skill Step 10.