2454186cc429524b9a198e88faa78be50f220b15
closes the phase-2 hardening list main.ts has been advertising since #122 (helmet + cors + csrf). two new middlewares + an alignment pass so every bff error follows a single response contract. structured error filter: global ExceptionFilter (registered via app.useGlobalFilters at the top of bootstrap()) normalises every 4xx/5xx response to: { error: { code, message, traceId } } - code: stable token the spa can switch on. either explicit on the HttpException's response object (UnauthorizedException({ code: 'unauthenticated', message })) or derived from status. 500s always 'internal'. - message: safe text. 500s NEVER leak the underlying exception (full message + stack go to pino's err: field, never to the response body). - traceId: otel trace id for cross-correlation. exported errorResponse(code, message) helper for raw-middleware callers (csrf, rate-limit) so the envelope is identical whether the response came through nest's filter or a short-circuit middleware. rate limiting: express-rate-limit mounted after the session middleware. - dynamic max per request: 10/min on /api/auth/login + /api/auth/ callback (RATE_LIMIT_AUTH_PER_MINUTE), 120/min elsewhere (RATE_LIMIT_PER_MINUTE). - bucket key = session id when auth, remote ip otherwise. rotating sessions doesn't dodge the limit; auth'd users get per-account fairness regardless of source ip. - /api/health skipped. orchestrator polls don't burn user quota. - 429 uses the shared errorResponse envelope. - in-memory store. single-instance v1 per adr-0015. redis-backed store is one config arg the day we scale out horizontally. alignment pass on existing error responses: - csrf middleware used to return { error: 'csrf' }. now uses errorResponse('csrf', 'CSRF token missing or invalid'). - /auth/me used to res.status(401).json({ error: 'unauthenticated' }) directly. now throws UnauthorizedException({ code: ..., message: ... }) so the filter formats it. identical shape on the wire. type-resolution fix (transitive): @types/express@4.17.25 was being pulled in by http-proxy-middleware (nx's webpack-dev-server). express-rate-limit's .d.ts files import 'express', and tsc was matching the v4 copy — causing Request type mismatches with our v5-based code. added "@types/express": "^5.0.6" to pnpm.overrides so the workspace pins one version everywhere. specs: - 199/199 portal-bff (was 174 before; +25 specs across StructuredErrorFilter, rate-limit middleware, csrf alignment, /me alignment). - feature-auth 28/28, portal-shell 34/34 unchanged. - clean-env repro: env -u every config var → 261/261 pass. out of scope: - redis-backed rate-limit store (single-instance v1). - per-user rate-limit overrides (admin / service accounts). - csp fine-tuning when caddy serves the static spas.
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%