feat(zgw): finish WP-52 OpenZaak Notificaties (NRC) webhook slice
CI / frontend (push) Successful in 2m33s
CI / backend (push) Successful in 1m45s
CI / storybook-a11y (push) Successful in 7m47s
CI / e2e (push) Successful in 4m3s
CI / semgrep (push) Successful in 1m7s
CI / api-client-drift (push) Successful in 2m3s

Endpoint/DTO/options landed already in c4dd846; this closes the loop with
NotificatieTests.cs (accept/reject/missing-header, asserting the AuthzAuditStore
row), missing appsettings.json keys (also backfills DrcBaseUrl/
InformatieobjecttypeUrls, stale since WP-51), and the webhook + abonnement
provisioning docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-30 08:07:50 +02:00
co-authored by Claude Sonnet 5
parent f356dc7329
commit bea04549dd
7 changed files with 243 additions and 84 deletions
+41 -5
View File
@@ -108,6 +108,37 @@ confidentiality level would matter for production but isn't needed to prove the
- `OpenZaakZaakSource.cs` — follows `{count,next,previous,results}` pagination, resolves +
caches zaaktype labels, attaches `Authorization: Bearer <jwt>`.
- `OpenZaakDocumentSource.cs` — DRC upload + zaak-link (WP-51), same auth/JSON pattern.
- `NotificatieDto.cs` + the `POST /api/v1/zgw/notificaties` endpoint (`Program.cs`, WP-52) — the
**inbound** NRC webhook, not a source/mapper: see the dedicated section below.
## Notificaties (NRC) webhook — inbound, WP-52
Unlike ZRC/ZTC/DRC (which the BFF calls outbound as a client), the Notificaties API calls
**this BFF** — OpenZaak POSTs a `NotificatieDto`-shaped body to `POST /api/v1/zgw/notificaties`
on every event on a subscribed kanaal. Auth is inverted too: no per-call JWT, just a fixed
shared secret compared in constant time (`CryptographicOperations.FixedTimeEquals`) against
`ZgwOptions.NotificatieAuthorization` — an unconfigured (empty) secret rejects every call,
never accepts. Every attempt (accept or reject) is written to the same `AuthzAuditStore` the
authz gate uses (`action="zgw:notificatie"`, `resource=hoofdObject` — a URL, not PII, `role="nrc"`)
via the store directly, since there's no `Principal` for an NRC caller to run through the
`AuditAuthz` helper.
There is no cache to invalidate today (`/admin/cases` and every other read already goes straight
to `IZaakSource` per call), so a valid notification's only visible effect right now is the audit
row proving the round-trip works end-to-end. Add real invalidation at the `// ponytail:` marker
in `Program.cs` if a cache is ever introduced.
**Provisioning the `abonnement` is out-of-band, one-time config against a live OpenZaak — not
app code.** Register it once (e.g. via OpenZaak's admin UI or a `POST` to its Abonnementen API)
pointing at this BFF's public URL:
```jsonc
{
"callbackUrl": "https://<this-bff>/api/v1/zgw/notificaties",
"auth": "<same value as Zgw:NotificatieAuthorization>",
"kanalen": [{ "filters": {}, "naam": "zaken" }],
}
```
## The five ZGW APIs (context for later slices)
@@ -162,7 +193,11 @@ default.
"InformatieobjecttypeUrls": {
"identiteit": "https://open-zaak.example/catalogi/api/v1/informatieobjecttypen/<uuid>",
"diploma": "https://open-zaak.example/catalogi/api/v1/informatieobjecttypen/<uuid>"
}
},
// WP-52 (Notificaties): NRC base URL (documentation/provisioning only, no outbound call) +
// the shared secret NRC must send back on every webhook POST.
"NrcBaseUrl": "https://open-zaak.example/notificaties/api/v1",
"NotificatieAuthorization": "<same value registered in the abonnement's `auth` field>"
}
```
@@ -198,10 +233,11 @@ Principles this demonstrates:
comment in `ZgwZaakMapper` show where the ACL is deliberately thin — an ACL need not be
complete on day one, but its shortcuts should be visible.
Caveat: `IZaakSource` covers the cases **read + create** path (WP-49/50) and `IDocumentSource`
covers **upload + zaak-link** (WP-51). Other BFF endpoints still read `SeedData`/static stores
directly — ACL-ready (the DTO seam exists) but not yet swappable. That is the WP-52 roadmap
(notificaties), plus the two cross-cutting WPs the arc needs for production: **WP-53** (a real
Caveat: `IZaakSource` covers the cases **read + create** path (WP-49/50), `IDocumentSource`
covers **upload + zaak-link** (WP-51), and the inbound `POST /zgw/notificaties` webhook
(WP-52) closes the read/write/document/notify arc. Other BFF endpoints still read
`SeedData`/static stores directly — ACL-ready (the DTO seam exists) but not yet swappable.
What's left in this arc is the two cross-cutting WPs production needs: **WP-53** (a real
per-request identity seam + citizen-scoping — today the owner/BSN is stubbed) and **WP-54** (a
docker OpenZaak harness + opt-in integration test — today everything is fixture/mock-tested
against no live instance).