Julien Gautier 707ff25ee0
CI / commits (pull_request) Successful in 2m58s
CI / scan (pull_request) Successful in 2m59s
CI / check (pull_request) Successful in 3m10s
CI / a11y (pull_request) Successful in 2m47s
CI / perf (pull_request) Successful in 5m56s
feat(portal-bff): admin audit-log read endpoint + audit_reader-locked path
First real consumer of the admin module — paginated audit-log viewer
per ADR-0020's v1 catalogue, gated by @RequireAdmin and emitting
admin.audit.query on every read as the "fishing expedition" deterrent
called out in ADR-0020 §"Audit log captures … Read actions are also
captured … to deter fishing expeditions".

What ships

- AuditReader (apps/portal-bff/src/admin/audit-reader.service.ts):
  - SET LOCAL ROLE audit_reader inside the transaction — symmetric
    with AuditWriter's SET LOCAL ROLE audit_writer. The runtime
    role contract holds even when the BFF connection is otherwise
    privileged; UPDATE/INSERT/DELETE on audit.events fail at the
    Postgres level.
  - Parameterised SELECT only — never concatenates filter values
    into SQL. Subject prefix uses LIKE with explicit ESCAPE and
    escapes %/_ in the literal so an admin-side wildcard can't
    masquerade as a meta-character.
  - COUNT(*) + LIMIT/OFFSET pagination. Order: created_at DESC,
    id DESC for deterministic page boundaries on identical
    timestamps.
  - Hard caps limit at MAX_LIMIT (200) even if a caller bypasses
    the DTO — defense in depth on the BFF's event loop.

- AdminAuditQueryDto (apps/portal-bff/src/admin/audit-query.dto.ts):
  - Filters: eventType, actorIdHash, audience, outcome,
    subjectPrefix, createdAtFrom/createdAtTo (ISO-8601).
  - Pagination: limit (1..200, default 50), offset (default 0).
  - Wired through Nest's global ValidationPipe so unknown query keys
    are rejected (forbidNonWhitelisted) and limit is bounded.

- AdminAuditController:
  - GET /api/admin/audit, @RequireAdmin at the class level.
  - Forwards the validated DTO to AuditReader, then emits
    admin.audit.query with { filters, resultCount } as payload so a
    reviewer can see what the admin searched for.
  - @RequireMfa intentionally NOT applied in v1: the admin surface
    already sits behind a freshly-MFA'd session at sign-in, and the
    per-query audit row is the deterrent. Adding @RequireMfa later
    is a one-line change.

- AuditWriter gains adminAuditQuery() typed method emitting the
  event with outcome=success (the read happened; whether it
  returned rows is captured separately in resultCount).

Tests: +30 specs (AuditReader 10, AdminAuditController 6, AuditWriter
typed event 2, DTO validation 12). All 308 BFF specs pass.
2026-05-14 16:07:02 +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%