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
45 lines
2.0 KiB
Markdown
45 lines
2.0 KiB
Markdown
# 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)
|
|
|
|
```bash
|
|
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.
|