Julien Gautier 9ba5815f1b
CI / scan (pull_request) Successful in 2m27s
CI / commits (pull_request) Successful in 2m44s
CI / check (pull_request) Successful in 2m54s
CI / a11y (pull_request) Successful in 2m15s
CI / perf (pull_request) Successful in 5m45s
feat(portal-bff): @RequireMfa decorator + freshness guard (ADR-0011)
Designed-in, dormant per ADR-0011 §"Step-up MFA". This PR ships the
guard, the decorator, and the audit integration — no v1 route uses
the decorator yet. First consumer will be the admin entry route per
ADR-0020 (`@RequireMfa({ freshness: 600 })`) once the distinct admin
session is in place.

Mechanics:

- `auth/mfa.ts` exports the documented `MFA_AMR_VALUES` allow-list
  (mfa, otp, fido, wia, phr) and `wasMultiFactor(amr)`. Adding a
  value is an ADR-recorded decision; the spec pins the list.

- `config/check-mfa-config.ts` reads `MFA_FRESHNESS_SECONDS` with
  default 600 s + minimum 60 s. Anything below the floor fails the
  validator (the floor catches "MFA on every navigation"
  misconfiguration).

- `RequireMfaGuard` (`auth/require-mfa.guard.ts`):
  - No session → 401 `unauthenticated`, no audit.
  - Session with no MFA-class `amr` → 401 `mfa_required` + audit
    `auth.mfa_required reason=no-mfa-in-amr`.
  - Session with no `mfaVerifiedAt` → 401 + audit `reason=no-mfa-verified-at`.
  - Session with stale `mfaVerifiedAt` → 401 + audit
    `reason=mfa-stale` (includes `mfaAgeMs` payload field).
  - Same audit-write propagation posture as `AdminRoleGuard`.

- The 401 carries `code: 'mfa_required'` in the structured error
  envelope. The `reason` discriminator is NOT surfaced over the
  wire — only the audit row carries it, so an attacker can't
  fingerprint sessions by probing.

- `RequireMfaOptions.freshness` overrides the env default per-route.
  Read via `Reflector.getAllAndOverride` with method-level metadata
  winning over class-level — Nest's standard merge.

- `WWW-Authenticate` header + MSAL claims-challenge blob (ADR-0011
  §"Step-up MFA — designed-in" step 2) defer to a later PR — they
  need MSAL Node integration AND the SPA interceptor to consume
  them. The structured `code: 'mfa_required'` is sufficient for the
  SPA to pivot on once the interceptor lands.

Session payload:

- `session.mfaVerifiedAt` added to the express-session augmentation
  in `session.types.ts`. Set to `Date.now()` at sign-in by the
  callback — Entra's CA policy is the authority on whether MFA
  actually happened; the BFF just stamps "now" when persisting a
  session whose `amr` reflects MFA. Refreshed by future step-up
  re-auth flows.

Tests: +37 specs (mfa helpers 9, config reader 9, guard 12, audit
typed method 3, callback assertion +1, +3 parametric expansions).
2026-05-14 01:29:37 +02:00

Documentation index

This is the entry point to all project documentation. It is maintained automatically: any addition, rename, or removal of a .md file under docs/ must be reflected here in the same change.

Conventions

  • Documentation is written in English.
  • One topic per file. Group related files into a folder when there are three or more.
  • Cross-reference with relative links so they keep working in GitHub, IDEs, and exported sites.
  • For architectural decisions, do not add them here — they belong in decisions/ as MADR 4.0.0 ADRs.

Sections

Daily development

  • development.md — repo layout, prerequisites, initial setup, daily commands, observability dev-loop (Jaeger UI, log ↔ trace correlation), dependency updates (Renovate), conventional commit cycle. Day-to-day reference for working on the project.

Architecture

  • architecture.md — cross-cutting Mermaid diagrams: C4 system context, C4 containers, Nx module boundaries, CI/CD pipeline. Single-decision diagrams (auth sequence, ERD, etc.) live inline in their ADR.

Onboarding & environment

Setup guides for new contributors:

Operations & runbooks

Empty — to be populated when we deploy.

Security, performance, accessibility

Empty — placeholders to be filled with rationale docs alongside their corresponding ADRs.

S
Description
No description provided
Readme 42 MiB
Languages
TypeScript 85.3%
JavaScript 5.4%
SCSS 4.3%
HTML 3.9%
Shell 0.8%
Other 0.3%