Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

03 — User Journeys (Auth Module)

End-to-end journeys for the Auth module computed from auth.controller.ts + auth.service.ts. Each journey: entry, intent, decision points, system responses, loading, failures, recovery, exit, back navigation, abandonment, timeout, session expiry, permission denial, offline. (planned) / (forward-looking) marks per global rules.


1. Login

entry: warm/cold app open, deep link, push→content, session-expiry redirect
intent: obtain an authenticated context
sequenceDiagram
    actor U as User
    participant F as LoginScreen
    participant R as AuthRepository
    participant API as POST /auth/login
    U->>F: enter email + password
    F->>R: submit()
    R->>API: {email, password}
    alt success (200)
        API-->>R: envelope.data = {accessToken, refreshToken}
        R-->>F: persist tokens (secure storage) + session meta
        F-->>U: navigate /home (role-aware)
    else 401 UNAUTHENTICATED
        API-->>R: "Invalid email or password."
        F-->>U: field-level error, inline (never reveals which field wrong)
    else 429 RATE_LIMITED
        F-->>U: countdown, no auto-retry
    else network
        F-->>U: AppErrorState + Retry
    end
  • Decision points: remember this device? → uses refresh in secure storage for auto-session; 2FA challenge if server returns one (planned).
  • Loading: button loading spinner replaces label; anti double-submit (08_Interaction_&_Motion.md §6).
  • Failure covers: wrong password, locked account ("Account locked. Try again later." — auth.service.ts:135), unknown email (identical text), rate limit, offline.
  • Exit: successful → /home (per-role landing, 00-shared/05 §2); back = quit.
  • Abandonment: back stack cleared after success; token loss → sessionExpired → re-login.
  • Password managers: autofill hints username+current-password, autofillHints on TextFields (09_Accessibility_Baseline.md §10).

2. First-login email verification

entry: in-app banner post-register, email click (deep link /login) — target contract
intent: satisfy "email verification mandatory before first login" (FIXED WIRE CONTRACT; OQ-1)
sequenceDiagram
    actor U as New user
    participant E as Email client
    participant W as EmailWorker (BullMQ emails)
    participant API as POST /auth
    U->>API: POST /auth/register
    API-->>W: UserRegistered -> emails/send-welcome-email
    W-->>E: Welcome email, token=verificationToken
    U->>E: see verify link (deep link /verify-email?token=...)
    E-->>F: open app VerifyEmailScreen
    F->>API: POST /auth/verify-email {token}
    alt token valid
        API-->>F: 200 {message:"Email verified successfully."} (auth.service.ts:235)
    else token invalid/expired
        API-->>F: 400 "Invalid or expired verification token."
    end
    F-->>U: success (check) → /login or /home
  • Resend: POST /auth/resend-verification (JWT) — 5/120s rate (auth.controller.ts:83-89).
  • Already-verified token: 200 "Email already verified." (auth.service.ts:215) — idempotent client handling.
  • Recovery: invalid token → inline error + "resend email" (rate-limited) + deep-link fallback.

3. TOTP 2FA — setup & challenge

2FA setup is fully implemented; login challenge is (planned) (server returns no challenge signal today — OQ-1).

sequenceDiagram
    actor U as Admin
    participant S as SecuritySettings
    participant API1 as POST /auth/2fa/enable
    participant API2 as POST /auth/2fa/verify
    U->>S: "Set up two-factor authentication"
    S->>API1: (JWT)
    API1-->>S: {secret, qrCodeUri} (otpauth URI, auth.service.ts:330)
    S-->>U: QR + manual `secret` — Save (copy) + pickup
    U->>APP: open authenticator, scan
    U->>S: enter 6-digit code
    S->>API2: POST /auth/2fa/verify {token}
    API2-->>S: 200 {message:"2FA enabled successfully."}
    S-->>U: green confirmation; TOTP badge ON
    Note over U: next login, issuer StudyLyon
  • Failure: wrong code → 401 "Invalid TOTP code."; already enabled → 400 "already enabled."; verify before enable → 400 "Call enable first." (auth.service.ts:326,350-353).
  • Disable: needs code prompt → POST /auth/2fa/disable (verified action).
  • Loss of device: no recovery codes today (planned) (IMPLEMENTATION_PLAN.md §Phase 4 /mfa/recovery-codes) → route to account recovery / admin un-enroll (planned).

4. Forgot / reset password

entry: "Forgot password?" on login; privacy: indistinguishable for unknown email
sequenceDiagram
    actor U as User
    participant F as ForgotPasswordScreen
    participant R as AuthRepo
    participant API1 as POST /auth/forgot-password
    participant E as Email (TOKEN reset link)
    participant P2 as ResetScreen
    participant API2 as POST /auth/reset-password
    U->>F: email
    F->>API1: {email}
    API1-->>F: 200 — identical body for existing/missing user
    API1->>E: PasswordResetRequested → emails worker, token 32B hex, 1h TTL
    U->>E: click link → app deep-link /reset-password?token=...
    P2->>API2: POST /auth/reset-password {token, newPassword(≥8)}
    API2-->>P2: 200 {message:"Password reset successfully."} — all sessions revoked
    P2-->>U: "Log in with new password" → /login
  • Rate: forgot 3/min, reset 5/min (auth.controller.ts:92-107).
  • Timeout: token expires 1 h (auth.service.ts:266, passwordResetExpiresAt) → 400 "Reset token has expired." → back to forgot.
  • Reuse: after successful reset passwordResetToken cleared (auth.service.ts:303); re-use → 400 "Invalid or expired reset token."
  • All devices: reset deletes every session — user must log in on every device (auth.service.ts:306).

5. Logout

entry: settings → "Log out" (all), per-device “Log out” from Sessions
sequenceDiagram
    participant U as User
    participant S as SecurityPage
    participant API as POST /auth/logout|logout-all
    S->>API: logout (refreshToken)          // delete 1 session by hash
    S->>API: logout-all                      // (JWT) deletes ALL sessions (user)
    API1-->>S: 200 {message:"Logged out successfully."}
    API-->>S: 200 {message:"Logged out from all devices."}
    S-->>U: clear local tokens → /login (+ "You have been logged out" snackbar)
  • Permission denial: logout-all is JWT-only; unauthenticated → 401 → redirect login.
  • Risks documented: the controllers' logout requires refreshToken — an empty token throws arbitrary Error → 500 (OQ-4); client must never submit blank.
  • Exit: no back into app shell.

6. Silent refresh / session expiry (token lifecycle)

stateDiagram-v2
    [*] --> Authenticated: login
    Authenticated --> Refreshing: accessToken expires
    Refreshing --> Authenticated: POST /auth/refresh ok
    Refreshing --> SessionExpired: refresh fails (reuse/expired/revoked)
    SessionExpired --> Login: show reason banner
    Login --> Authenticated: fresh login
    Authenticated --> LoggedOut: logout / revoked on another device
  • Single-flight refresh on 401 interceptor (dio refresh interceptor, 00-shared/11 §5).
  • Refresh rotation is implicit: every successful refresh yields a new pair; old refresh is revoked server-side (auth.service.ts:190-196).
  • Expired refresh (4xx) → SessionExpired → re-login + reason snackbar (if known).

7. Device / session management

entry: Settings → Security → Devices
GET /auth/sessions → list (deviceName/browser/operatingSystem/platform/ipAddress/location
from user_sessions, created/expires/lastActivity)
  • Item → "Log out this device" → confirm dialog → DELETE /auth/sessions/:id.
  • "Log out all other devices" (would be (planned) — today there's only logout-all-like and revoke-by-id on the current user).
  • Data note: session list returns all of the current user's sessions sorted as-in-insertion (no page param — small list, client renders grouped by device).

8. API key management

entry: Security → API keys
  • List: GET /api/v1/api-keys → cards (name, prefix abc12345…, scopes, createdAt, last); only active.
  • Create: POST /api/v1/api-keys → one-time modal shows full key (copy); never re-displayed.
  • Revoke: DELETE /api/v1/api-keys/:id → confirm.

9. Tenant onboarding → first login (register)

entry: `/register` (fresh install / SaaS landing)
  • POST /auth/register → tokens returned → already authenticated in tenant.
  • Decision: some tenants may want admin to pick plan/channel before auth — no signal; assume immediate.
EntryBehaviorStatus
Deep link studylyon://verify-email?token= / /reset-password?token=parse token, preload form(forward-looking) client; no server link endpoint
Push → "session revoked on another device"openssessions; re-auth(planned) push infra 00-shared/12 B3
QR scan for 2FAonly 2FA setup QR display (generated client-side); scanner not neededdisplay-only

Abandonment & exit rules (all): back = cancel token capture; timeout = 15 min idle reset token expired → 400; permission denial impossible within module (self-service); offline = cached verify/reset state only, actions blocked (write queue not defined for auth).