Files
register-referentie/docs/synthetic-data.md
T
not d0fb2b3e8c
CI / build (push) Successful in 1m7s
CI / lint (push) Successful in 1m22s
CI / unit (push) Successful in 1m24s
CI / frontend (push) Successful in 3m5s
CI / mutation (push) Successful in 6m13s
CI / verify-stack (push) Successful in 8m39s
S-15c · Enforce MFA on the medewerker (Keycloak) realm (#158)
Closes #132.

Staff logins (behandel + beheer portals) now need a second factor; the citizen realms are unchanged.

**How:** every seeded medewerker carries a TOTP credential, which activates Keycloak's stock *conditional OTP* step in both the browser flow and the direct grant — no custom browser-flow JSON in the export. `CONFIGURE_TOTP` is a default required action so a medewerker added later must enrol first. ADR-0031 records the choice and, explicitly, that the shared fixture secret is a demo posture only.

**Tests (red first, 30c5279):**
- `check_realms.py` asserts the medewerker password-only grant is **refused**, then that password + TOTP succeeds and still carries the `behandelaar` role. It failed with `[MFA NOT ENFORCED]` against the old export.
- The three medewerker e2e logins move to `loginMedewerker()` (`tests/e2e/medewerker-login.ts`), which submits Keycloak's OTP prompt. Both TOTP implementations (Python `hmac`, Node `crypto`) are ~6 lines of RFC 6238 — no new dependency.

Verified locally against Keycloak 26.1: password-only → `invalid_grant`, password + code → 200, and the browser flow's `#otp` prompt accepts a computed code and issues an auth code.

## Definition of Done
- [x] Failing test/verify committed first; implementation makes it pass.
- [x] Conventional Commits referencing the issue (`refs #132`).
- [ ] CI green (verify-stack compose smoke + relevant checks).
- [x] `docker compose up` reaches green health within 3 minutes (Keycloak change is import-time only).
- [x] Docs touched (runbook, synthetic-data, demo-script) + ADR-0031 + demo note.
- [x] Closed by the merging PR (`closes #132`).

🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #158
2026-09-04 08:27:52 +00:00

2.0 KiB

Synthetic data

All credentials here are dev-only synthetic test data — never real personal data, never used outside local development.

Keycloak realms (S-02)

Keycloak runs at http://localhost:8180 (admin console: admin / admin). Four realms are imported at boot from infra/keycloak/realms/. Each has a public OIDC client big-portal (standard flow + direct access grants enabled, redirect URIs * for dev).

All test users share the password test123.

Realm Mimics User Identifying claim
digid DigiD (burgers) jan-burger bsn = 123456782
digid DigiD (burgers) sanne-burger bsn = 231477813 (S-26 resume e2e — its own user so it can leave an open registration)
eherkenning eHerkenning (bedrijven) acme-ondernemer kvk = 12345678
eidas eIDAS (EU) pierre-dupont eidas_id = FR/NL/AB-1234-5678
medewerker Internal staff merel-behandelaar role behandelaar
medewerker Internal staff tom-teamlead roles behandelaar, teamlead
medewerker Internal staff bram-beheerder role beheerder

medewerker users additionally need a second factor: that realm enforces MFA (S-15c, ADR-0031). All three share the fixture TOTP secret BIGMEDEWERKEROTPSEED; print a current code with python3 infra/keycloak/check_realms.py otp.

The identifying claims are injected via OIDC protocol mappers on big-portal (user-attribute → token claim); medewerker roles appear in realm_access.roles.

Get a token (for testing)

curl -s -X POST \
  http://localhost:8180/realms/digid/protocol/openid-connect/token \
  -d grant_type=password -d client_id=big-portal \
  -d username=jan-burger -d password=test123 -d scope=openid | jq -r .access_token

For a medewerker user, add -d totp=$(python3 infra/keycloak/check_realms.py otp) — without it the grant is refused with invalid_grant.

Decode the JWT payload to see the bsn claim. make keycloak-smoke checks every realm automatically.