Two backlog trees are complete: `docs/project/backlog/` (75 files, every WP done) and `docs/project/refactor-backlog-setup/` (the arc before it). Move both under `docs/project/archive/` with `git mv`, so history stays intact through `git log --follow`. `SHOWCASE-ROADMAP.md` moves with them, because it points at the now-archived backlog README. Add `docs/project/archive/README.md`. It states that these trees are historical and names the two directories that are still live. Repoint every inbound reference named in RD-30's Files table: CLAUDE.md, the root README, both backend READMEs, `LetterHtml.cs`, `a11y.mdx`, the `document-feature` and `new-ssp` skills, and the readable-codebase PLAN, README, and RD-19 ticket. Fix two upward-relative links inside the moved WP files (WP-68, WP-69) that gained a directory level and would otherwise break. Repoint `.prettierignore`'s two agent-prompt exclusions to their new path, so prettier keeps leaving those files' exact wording alone. Mark RD-30 done and check off its acceptance criteria; flip its README row to done. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
97 lines
4.7 KiB
Markdown
97 lines
4.7 KiB
Markdown
# WP-62 — Backend: medewerker caller identity + authz seam
|
|
|
|
Status: done
|
|
Phase: 11 — Behandelportal
|
|
|
|
## Why
|
|
|
|
The backend's only identity today is `CallerIdentity` (BSN + display name, from WP-53)
|
|
modeling a single zorgverlener actor. ADR-0002 requires a second actor kind
|
|
(medewerker/employee) that authenticates differently (no BSN, has `rollen`) and needs its
|
|
own capability checks for backoffice calls. This slice adds that identity + authz surface
|
|
on the backend only — unused by any frontend until WP-64 calls it, matching the same
|
|
"seam, not provider" discipline WP-53 used for citizen identity (stub, no real employee
|
|
SSO — out of scope per CLAUDE.md, same as DigiD).
|
|
|
|
## Read first
|
|
|
|
- [ADR-0002 §3 — Principal union](../reference/architecture/0002-user-groups-and-bounded-contexts.md)
|
|
- `backend/src/BigRegister.Api/Domain/Authorization/CallerIdentity.cs`,
|
|
`IIdentityProvider.cs`, `StubIdentityProvider.cs` (WP-53's pattern to extend/mirror)
|
|
- [WP-53](WP-53-inbound-identity-and-citizen-scoping.md)
|
|
|
|
## Decisions (pre-made, don't relitigate)
|
|
|
|
- Model the two actor kinds as a discriminated union (mirroring ADR-0002 §3:
|
|
`{ kind: 'zorgverlener'; bsn }` | `{ kind: 'medewerker'; medewerkerId; rollen }`),
|
|
backend-side, extending `CallerIdentity` rather than introducing a parallel type.
|
|
- Stub the medewerker identity the same way WP-53 stubbed citizen identity (a
|
|
header-driven `StubIdentityProvider` variant) — no real employee SSO/eHerkenning.
|
|
- New capability checks (e.g. `canBeoordelen`) are computed backend-side and exposed only
|
|
as decision flags, never a permission matrix shipped to a frontend (ADR-0001 discipline,
|
|
reaffirmed by ADR-0002 §3).
|
|
|
|
## Files
|
|
|
|
- `Domain/Authorization/CallerIdentity.cs` (extend to the union)
|
|
- `Domain/Authorization/StubIdentityProvider.cs` (medewerker variant)
|
|
- `Domain/Authorization/Authz.cs` (medewerker capability checks)
|
|
- Tests
|
|
|
|
## Steps
|
|
|
|
1. Extend `CallerIdentity` to the two-actor-kind union.
|
|
2. Extend the stub identity provider to produce a `medewerker` identity from a
|
|
header/config, alongside the existing zorgverlener stub.
|
|
3. Add capability checks a backoffice caller needs (start with `canBeoordelen`; extend as
|
|
WP-65 needs more).
|
|
4. Unit tests for both identity kinds and the new capability checks — no consumer exists
|
|
yet (WP-64+ will call this).
|
|
|
|
## Acceptance criteria
|
|
|
|
- [x] `CallerIdentity` represents both actor kinds without breaking any existing
|
|
zorgverlener call site (WP-53's tests still green).
|
|
- [x] A stub medewerker identity resolves from a request header, mirroring the existing
|
|
citizen stub.
|
|
- [x] At least one capability flag (`canBeoordelen`) computable for a medewerker
|
|
identity, unit-tested.
|
|
|
|
## Verification
|
|
|
|
`cd backend && dotnet test` (existing WP-53 tests unaffected + new medewerker tests
|
|
green) — 182/182 (168 baseline + 14 new). `dotnet format --verify-no-changes` clean.
|
|
|
|
## Out of scope
|
|
|
|
Any actual backoffice endpoint using this (WP-64+); real employee SSO/eHerkenning.
|
|
|
|
## Risks
|
|
|
|
If the union is modeled as a bolt-on rather than replacing the flat type, existing
|
|
zorgverlener call sites could break — mitigated by keeping WP-53's existing tests as a
|
|
regression gate.
|
|
|
|
## Outcome notes
|
|
|
|
- **The `Files` list undersold the blast radius.** `CallerIdentity` became `abstract`
|
|
with two derived records (`ZorgverlenerCaller`, `MedewerkerCaller`), which is a hard
|
|
compile error at every `new CallerIdentity(...)` and every `.Bsn` read outside
|
|
`Domain/Authorization/` — 17 `ctx.Caller().Bsn` reads in `Program.cs` alone, plus 6
|
|
seam signatures (`IDocumentSource.Upload`, `IZaakSource.ListMyCases` and their
|
|
Local/OpenZaak implementations) narrowed to `ZorgverlenerCaller` where `.Bsn` is used
|
|
as an ownership key, plus test fixtures in 4 test files.
|
|
`CallerIdentity.SubjectId` (BSN or medewerkerId) is the trick that kept the
|
|
token-mint-only call sites (`ZgwTokenProvider.Mint`, `ZgwHttpClient`, `IZaakSource
|
|
.CreateZaak`, `IDocumentSource.LinkToZaak`) compiling with zero signature changes —
|
|
they never needed the BSN specifically, just _an_ id for the ZGW audit trail.
|
|
- **`Role` (`PrincipalRole`, the existing dev-role stand-in) stays on the base record**,
|
|
not per-variant — it's an orthogonal axis (both actor kinds can be any dev role),
|
|
which is why `Authz.ResolvePrincipal(ctx) => new(ctx.Caller().Role)` and its ~15
|
|
call sites needed no changes at all.
|
|
- **A new extension, `ctx.Zorgverlener()`**, narrows `CallerIdentity` to
|
|
`ZorgverlenerCaller` or throws — deliberately a 500, not a 403, since no medewerker
|
|
reaches any SSP endpoint today (nothing sends `X-Medewerker` yet). WP-64 should
|
|
map this to a 403 once real backoffice traffic exists; flagging it now so it isn't
|
|
mistaken for an oversight.
|