feat(privacy): WP-41 — persisted, queryable authz/PII-reveal audit

Persist the security-relevant events (authz denials + BIG-nummer reveal/step-up) into a
data-minimised EF table (AuthzAuditEntry: At/Action/Resource/Decision/Role/CorrelationId —
never a name/BSN/value), extending the DocumentStore AuditEntry pattern (migration AuthzAudit).
AuditAuthz now persists via AuthzAuditStore.Record alongside its log line. GET /admin/audit
(admin-gated by the existing CasesAdmin) returns the trail newest-first. +3 backend tests incl.
a schema-carries-no-PII reflection test. Typed client regenerated (audit() + AuthzAuditDto);
no FE consumer yet (a future audit view must add the ROLE_AWARE prefix). Finishes WP-42's audit half.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-23 15:53:33 +02:00
co-authored by Claude Opus 4.8
parent 8c54ede6eb
commit 0f30143c5d
12 changed files with 590 additions and 5 deletions
+1 -1
View File
@@ -85,7 +85,7 @@ for its existing violations, so every WP ends green.
| [WP-38](WP-38-dependency-graph-boundaries.md) | Dependency graph + declarative boundaries (visualize + enforce) | 8 · platform/DX/showcase | done |
| [WP-39](WP-39-showcase-snippets-animations.md) | Showcase: linked code snippets + teaching animations | 8 · platform/DX/showcase | done |
| [WP-40](WP-40-pii-kernel.md) | PII kernel: branded `Bsn` VO (elfproef) + masked-value atom | 8 · platform/DX/showcase | done |
| [WP-41](WP-41-persisted-authz-audit.md) | Persisted, queryable authz/PII-reveal audit (no PII) | 8 · platform/DX/showcase | todo |
| [WP-41](WP-41-persisted-authz-audit.md) | Persisted, queryable authz/PII-reveal audit (no PII) | 8 · platform/DX/showcase | done |
| [WP-42](WP-42-privacy-security-showcase.md) | Privacy & security showcase page (mask + no-PII log) | 8 · platform/DX/showcase | partial |
| [WP-43](WP-43-scaffold-generators.md) | Runnable generators: value-object / form-machine / bff-endpoint / ui-component | 8 · platform/DX/showcase | todo |
| [WP-44](WP-44-context-generator.md) | Runnable generator: `gen:context` | 8 · platform/DX/showcase | todo |
@@ -1,10 +1,22 @@
# WP-41 — Persisted, queryable authz/PII-reveal audit
Status: todo
Status: done
Phase: 8 — platform/DX/showcase
Priority: P2
Depends on: WP-40
## Outcome
New data-minimised EF table `AuthzAuditEntry` (`Data/AuthzAuditStore.cs`, DbSet + key config in
`AppDbContext`, migration `AuthzAudit`): `At, Action, Resource, Decision, Role, CorrelationId`
**never** a name/BSN/value. `AuditAuthz` now persists (via `AuthzAuditStore.Record`) alongside its
log line, so every authz denial + BIG-nummer reveal/step-up attempt is captured. `GET /admin/audit`
(admin-gated by the existing `CasesAdmin`/`cases:manage` — a dedicated `audit:read` cap is a later
refinement) returns the trail newest-first (client-side sort — SQLite can't ORDER BY DateTimeOffset).
+3 backend tests (deny recorded, reveal recorded, **schema-carries-no-PII** reflection test). Typed
client regenerated (`audit()` + `AuthzAuditDto`). No FE consumer yet — a future audit view (WP-42
finish) must add `/api/v1/admin/audit` to the `role.interceptor` ROLE_AWARE list or it silently 403s.
## Why
The security-relevant events (authz denials via `AuditAuthz`, BIG-nummer reveal, step-up) are
@@ -29,6 +41,6 @@ covers document lifecycle only. PRD-0002 §8 calls for a persisted authorization
## Acceptance criteria
- [ ] Denials, reveals, and step-up attempts land as rows with no PII/value fields.
- [ ] A test asserts the schema carries no name/bsn/value column.
- [ ] `dotnet test` + `npm run ci` green; api-client drift clean if endpoints added.
- [x] Denials, reveals, and step-up attempts land as rows with no PII/value fields.
- [x] A test asserts the schema carries no name/bsn/value column.
- [x] `dotnet test` (132) + `npm run ci` green; api-client drift clean after commit.