f50d2d66c03373b2481623910de3c9a9d9e539e5
Phase-3a step per ADR-0020 §"Sessions — distinct from `portal-shell`".
Wires a second `express-session` middleware on `/api/admin/*` carrying
`__Host-portal_admin_session` over Redis prefix `session:admin:` and
ships the parallel `/api/admin/auth/{login,callback,me,logout}` flow
that populates it. Signing in to one surface no longer signs the user
into the other — Entra SSO at the IdP level still preserves the
click-through experience.
What lands
- `session/admin-session-cookie.ts`: `adminSessionCookieName()` mirrors
the existing user-portal pattern (`__Host-` prefix in prod, plain
name in dev).
- `SessionModule` provides two parallel `express-session` instances
via a shared `buildSessionMiddleware()` factory:
SESSION_MIDDLEWARE cookie portal_session prefix session:
ADMIN_SESSION_MIDDLEWARE cookie portal_admin_session prefix session:admin:
The TTL policy, encryption key, signing secret, and session-id
entropy are unchanged — only the cookie name + Redis key prefix
differ.
- `main.ts` mounts a tiny path-routed dispatch: requests under
`/api/admin` get the admin session, everything else gets the user
one. Running both middlewares unconditionally would have the second
overwrite `req.session` from the first, collapsing the two surfaces.
- `EntraConfig` gains `adminRedirectUri` + `adminPostLogoutRedirectUri`,
validated at boot. The validator refuses to start when admin and
user redirect URIs collide (would silently fuse the two surfaces).
Both URIs must be registered on the same Entra app registration.
- `AuthService.{beginAuthCodeFlow,completeAuthCodeFlow,buildLogoutUrl}`
now take their redirect / post-logout URI as a parameter. Callers
pick which set to pass.
- New shared service `SessionEstablisher`:
establish(user, req, res, surface) — full sign-in recipe: mint
CSRF, populate session fields, save, register in
user_sessions index, emit auth.sign_in audit, log.
destroy(actor | undefined, req) — sign-out recipe: when actor
is set, remove from index + emit auth.sign_out audit; always
destroy the session (with Redis-hiccup tolerance).
Both `AuthController` and the new `AdminAuthController` call it —
no duplication of the 150-LOC session lifecycle logic.
- `AdminAuthController` mounts `/api/admin/auth/{login,callback,me,logout}`.
Structurally identical to `AuthController` but passes
`adminRedirectUri` / `adminPostLogoutRedirectUri` and clears the
admin session cookie on logout. `me` exposes the `roles` claim
(the SPA needs it for conditional admin UI); the user-portal `me`
intentionally still doesn't.
New env vars (mandatory at boot)
- ENTRA_ADMIN_REDIRECT_URI
- ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI
Tests: +25 specs (admin cookie 3, session-establisher 11, admin auth
controller 9, entra config 2). Existing AuthController tests
preserved through the refactor by passing a real `SessionEstablisher`
constructed with the same audit / index / logger mocks.
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:
- setup/01-wsl-terminal-setup.md — modern WSL terminal (Zsh + Powerlevel10k + CLI tools)
- setup/02-dev-web-stack.md — Node via nvm, pnpm via corepack, Docker
- setup/03-angular-nx-monorepo.md — Angular + Nx monorepo bootstrap
Operations & runbooks
Empty — to be populated when we deploy.
Security, performance, accessibility
Empty — placeholders to be filled with rationale docs alongside their corresponding ADRs.
Description
Languages
TypeScript
85.3%
JavaScript
5.4%
SCSS
4.3%
HTML
3.9%
Shell
0.8%
Other
0.3%