Three how-it-works/how-to-extend reference docs (docs/reference/), each with a coupling section, indexed in docs/README.md. New document-feature skill so docs ship in the same diff as the code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.3 KiB
Audit log (authz / PII-reveal trail) — how it's built & extended
A persisted, data-minimised trail of authorization decisions and sensitive reveals. By construction it never records a name, BSN, or the revealed value — only that a decision happened, on what kind of resource, and its outcome. Built in WP-41 (persisted backend) and surfaced by WP-42 (privacy/security showcase); it backs PRD-0002 — ABAC §8.
The one rule: one producer hub, not scattered logging
Every audited event flows through a single helper — AuditAuthz(…) in Program.cs.
It writes the log line and persists the row. Endpoints don't hand-roll audit records;
they call the hub. That's what keeps the "never log PII" guarantee enforceable in one place
instead of trusting every call site.
Layered pipeline
Backend:
Data/AuthzAuditStore.cs—AuthzAuditEntry (At, Action, Resource, Decision, Role, CorrelationId)+Record(…)/List()(newest-first). No PII column — a reflection test asserts the schema stays that way.Data/AppDbContext.cs—AuthzAuditDbSet + migration (persists to SQLite, WP-22).Program.cs:AuditAuthz(…)(the hub, ~line 590) is called on every authz denial and on the BIG-nummer reveal/step-up;GET /admin/audit(read) is gated byCasesAdmin.
Frontend (src/app/beheer/):
domain/audit-entry.ts→infrastructure/audit.adapter.ts(list()+parseAuditEntriesboundary) →application/audit.store.ts(root singleton,RemoteData, read-only) →ui/audit.page.ts(read-only table, gated oncases:manage, loads via a guardedeffectonce the capability resolves).
How to audit a new action
You do not touch the frontend. In the backend, at the decision point, call the hub:
AuditAuthz(ctx, action: "flags:manage", resource: key, decision: "deny");
Use a short stable action slug and a non-PII resource identifier (an id or table
name, never a name/BSN). The admin table picks it up automatically.
Coupling
Deep-linked on the producer side by design, self-contained on the consumer side:
- Consumer coupling: near zero. The audit store is read by exactly one page; no other component depends on it.
- Producer coupling: centralized, not scattered. Many endpoints call
AuditAuthz(stamdata/cases/flags denials, org-template edits, reveal-bignummer) — but all through the one hub, so it's a spoke-and-hub, not logic sprinkled across the codebase. Extend by calling the hub; never inline a new audit write.
Wire-up gotchas (both bit WP-41)
- Add the read endpoint to the
role.interceptorROLE_AWARE list (/api/v1/admin/audit) or it 403s silently with noX-Roleheader. - The admin nav entry lives in
ADMIN_LINKS, gated oncases:manage.
See also
- PRD-0002 — ABAC §8 — the audit requirement.
- WP-41 (persistence), WP-42 (showcase).
backend/src/BigRegister.Api/Data/AuthzAuditStore.cs— the no-PII schema.src/app/beheer/ui/audit.page.ts— the read-only view.- Roles & access —
cases:manage+ admin gating.