[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
+82 -10
View File
@@ -1,20 +1,92 @@
# Flow: User Login
# Flow: User Login (dual token + MFA)
> **Cycle 2 (2026-05-14)**: rebuilt around the AZ-531 + AZ-532 + AZ-534 + AZ-536 + AZ-537 stack. Single-token, SHA-384, HS256 path is gone. See `_docs/02_document/system-flows.md` F1 for the full narrative; this file is the canonical sequence diagram.
```mermaid
sequenceDiagram
participant Client
participant Mid as RateLimiter (per-IP, AZ-537)
participant API as Admin API
participant US as UserService
participant Sec as Security (Argon2id, AZ-536)
participant AL as AuditLog
participant Mfa as MfaService
participant RT as RefreshTokenService
participant Auth as AuthService (ES256, AZ-532)
participant SS as SessionService
participant DB as PostgreSQL
participant Auth as AuthService
Client->>API: POST /login {email, password}
Client->>Mid: POST /login {email, password}
Mid->>Mid: per-IP sliding-window check
alt no permits
Mid-->>Client: 429 + Retry-After
end
Mid->>API: forward
API->>US: ValidateUser(request)
US->>DB: SELECT user WHERE email = ?
DB-->>US: User record
US->>US: Compare password hash (SHA-384)
US-->>API: User entity
API->>Auth: CreateToken(user)
Auth-->>API: JWT string (HMAC-SHA256)
API-->>Client: 200 OK {token}
US->>DB: SELECT users WHERE email = ?
US->>AL: CountRecentFailedLogins(email, window)
alt account locked OR per-account threshold exceeded
US-->>API: AccountLocked / LoginRateLimited (RetryAfterSeconds)
API-->>Client: 423 / 429 + Retry-After
end
US->>Sec: VerifyPassword(presented, stored)
alt VerifyResult.Ok=false
US->>AL: RecordLoginFailed
US->>DB: UPDATE failed_login_count++; lockout_until = now + LockoutSeconds (if newly over)
US-->>API: WrongPassword (or NoEmailFound)
API-->>Client: 409
end
alt VerifyResult.NeedsRehash=true (legacy SHA-384)
US->>Sec: HashPassword (Argon2id)
US->>DB: UPDATE password_hash (lazy migrate)
end
US->>AL: RecordLoginSuccess
US->>DB: UPDATE failed_login_count = 0, lockout_until = NULL, last_login = now
US-->>API: User
alt user.MfaEnabled
API->>Mfa: IssueMfaStepToken(userId)
Mfa-->>API: ES256 JWT (mfa_pending=true, audience=mfa-step, ~5 min)
API-->>Client: 200 OK MfaRequiredResponse {mfa_required, mfa_token, expires_in: 300}
Note over Client,API: --- second factor ---
Client->>Mid: POST /login/mfa {mfa_token, code}
Mid->>Mid: per-IP sliding-window check
Mid->>API: forward
API->>Mfa: ValidateMfaStepToken(mfa_token) -> userId
API->>US: GetById(userId) -> User
API->>Mfa: VerifyForLogin(userId, code)
Mfa->>DB: TOTP verify decrypted mfa_secret OR consume recovery code
Mfa->>AL: RecordMfaLoginSuccess (or MfaRecoveryUsed)
Mfa-->>API: amr = ["pwd","mfa"] (+ "recovery" if used)
API->>RT: IssueForNewLogin(userId, mfaAuthenticated=true)
RT->>DB: INSERT INTO sessions (id, family_id=id, refresh_hash=SHA256(opaque), expires_at, mfa_authenticated=true)
RT-->>API: (opaqueRefreshToken, Session)
API->>Auth: CreateToken(user, sid=Session.Id, jti, amr=["pwd","mfa"])
Auth-->>API: AccessToken (ES256)
opt user.Role == CompanionPC
API->>SS: RevokeMissionsForAircraft(user.Id)
end
API-->>Client: 200 OK LoginResponse {AccessToken, AccessExp, RefreshToken, RefreshExp}
else
API->>RT: IssueForNewLogin(userId, mfaAuthenticated=false)
RT->>DB: INSERT INTO sessions (..., mfa_authenticated=false)
RT-->>API: (opaqueRefreshToken, Session)
API->>Auth: CreateToken(user, sid=Session.Id, jti, amr=["pwd"])
Auth-->>API: AccessToken (ES256)
opt user.Role == CompanionPC
API->>SS: RevokeMissionsForAircraft(user.Id)
end
API-->>Client: 200 OK LoginResponse {AccessToken, AccessExp, RefreshToken, RefreshExp}
end
```
## Related diagrams (cycle 2)
- `flow_refresh_token.md` *(see system-flows.md F11)*
- `flow_logout_revocation.md` *(see system-flows.md F12)*
- `flow_mission_token.md` *(see system-flows.md F13)*
- `flow_mfa_lifecycle.md` *(see system-flows.md F14)*
- `flow_revocation_snapshot.md` *(see system-flows.md F15)*
These are documented inline in `system-flows.md` rather than as standalone files; this `flow_login.md` is kept as a separate file because it is referenced from multiple ADRs and the security report.