Files
admin/_docs/02_document/diagrams/flows/flow_login.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

4.1 KiB

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.

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

    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 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
  • 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.