[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>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 09:22:53 +03:00
parent c2c659ef62
commit a77b3f8a59
35 changed files with 3624 additions and 468 deletions
@@ -1,38 +1,68 @@
# Module: Azaion.Common.Configs.JwtConfig
# Module: Azaion.Common.Configs.JwtConfig + SessionConfig
## Purpose
Configuration POCO for JWT token generation parameters, bound from `appsettings.json` section `JwtConfig`.
Configuration POCOs for JWT signing/validation and refresh-token TTLs. Bound from `appsettings.json` sections `JwtConfig` and `SessionConfig`. Both classes live in `Azaion.Common/Configs/JwtConfig.cs`.
> **Cycle 2 (2026-05-14) note (AZ-531 / AZ-532)** — major reshape:
> - HS256 shared-secret signing is gone. `Secret` is no longer read by any code path; the property is retained only as a temporary rollback escape hatch (AZ-532 spec).
> - New: `KeysFolder` (PEM directory) and `ActiveKid` (currently-signing key id) for ES256.
> - New: `AccessTokenLifetimeMinutes` (default 15) replaces the old `TokenLifetimeHours` (default 4) — short-lived access tokens are now paired with refresh-token rotation.
> - New companion class `SessionConfig` carries refresh-token TTLs.
## Public Interface
| Property | Type | Description |
|----------|------|-------------|
| `Issuer` | `string` | Token issuer claim |
| `Audience` | `string` | Token audience claim |
| `Secret` | `string` | HMAC-SHA256 signing key |
| `TokenLifetimeHours` | `double` | Token expiry duration in hours |
### JwtConfig
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `Issuer` | `string` | (required) | Token `iss` claim. Validated by JwtBearer middleware. |
| `Audience` | `string` | (required) | Token `aud` claim for interactive sessions. (Mission tokens override to `satellite-provider`; MFA step-1 tokens override to `azaion-mfa-step2`.) |
| `KeysFolder` | `string` | `secrets/jwt-keys` | Directory containing one ES256 PEM per key. The kid is the filename without `.pem`. |
| `ActiveKid` | `string?` | `null` | Kid currently used to sign new tokens. If null, falls back to the first PEM by ordinal filename order with a startup log warning. |
| `AccessTokenLifetimeMinutes` | `int` | 15 | Access-token TTL. |
### SessionConfig
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `RefreshSlidingHours` | `int` | 8 | Each rotation extends `expires_at` by this many hours from `now`. |
| `RefreshAbsoluteHours` | `int` | 12 | Family is rejected past this many hours since `family_started_at`, regardless of sliding rotations. |
## Internal Logic
None — pure data class.
None — pure data classes.
## Dependencies
None.
## Consumers
- `Program.cs` — reads `JwtConfig` to configure JWT Bearer authentication middleware
- `AuthService.CreateToken` — uses Issuer, Audience, Secret, TokenLifetimeHours to build JWT tokens
- `Program.cs`
- reads `JwtConfig` eagerly to fail-fast on missing Issuer/Audience and to construct the `JwtSigningKeyProvider` before `app.Build()`
- registers `Configure<JwtConfig>` and `Configure<SessionConfig>` for downstream injection
- `JwtSigningKeyProvider` — reads `KeysFolder`, `ActiveKid`
- `AuthService.CreateToken` — reads `Issuer`, `Audience`, `AccessTokenLifetimeMinutes`
- `RefreshTokenService` — reads `SessionConfig.RefreshSlidingHours`, `RefreshAbsoluteHours`
- `MfaService.IssueMfaStepToken` / `ValidateMfaStepToken` — reads `Issuer` (audience is hard-coded to `azaion-mfa-step2`)
- `MissionTokenService.MintToken` — reads `Issuer` (audience is hard-coded to `satellite-provider`)
## Data Models
None.
## Configuration
Bound via `builder.Configuration.GetSection(nameof(JwtConfig))`. Expected env var: `ASPNETCORE_JwtConfig__Secret`.
Bound via `builder.Configuration.GetSection(nameof(JwtConfig))` and `Configure<SessionConfig>`. Override via env vars:
- `JwtConfig__Issuer=…`, `JwtConfig__Audience=…`, `JwtConfig__KeysFolder=/var/lib/azaion/jwt-keys`, `JwtConfig__ActiveKid=kid-2026-05-14`
- `SessionConfig__RefreshSlidingHours=8`, `SessionConfig__RefreshAbsoluteHours=12`
## External Integrations
None.
Filesystem (read-only on `KeysFolder`).
## Security
`Secret` is the symmetric signing key for all JWT tokens. Must be kept secret and sufficiently long for HMAC-SHA256.
- Private signing keys live on disk only; the JWKS endpoint exports only public components. `chmod 600` is applied by `scripts/generate-jwt-key.sh`.
- The legacy `Secret` field is retained but unused; remove on a follow-up cleanup ticket once the rollback window has closed.
- `RefreshAbsoluteHours` is the hard cap on session lifetime — no rotation can extend past it. Bumping above 12 h needs a security review because it directly extends the leak-window of any one refresh token.
## Tests
None.
- `e2e/Azaion.E2E/Tests/JwksTests.cs` — exercises the rotation overlap (AC-3) by manipulating `KeysFolder` and `ActiveKid`.
- `e2e/Azaion.E2E/Tests/RefreshTokenTests.cs` — exercises both the sliding and absolute caps (AC-4).