S-15c · Enforce MFA on the medewerker (Keycloak) realm #158

Merged
not merged 7 commits from feat/132-medewerker-mfa into main 2026-09-04 08:27:53 +00:00
7 Commits
Author SHA1 Message Date
not b76035ebfe Merge remote-tracking branch 'origin/feat/132-medewerker-mfa' into feat/132-medewerker-mfa
CI / build (pull_request) Successful in 1m7s
CI / lint (pull_request) Successful in 1m22s
CI / unit (pull_request) Successful in 1m24s
CI / frontend (pull_request) Successful in 3m6s
CI / mutation (pull_request) Successful in 6m11s
CI / verify-stack (pull_request) Successful in 8m52s
2026-09-04 09:56:05 +02:00
notandClaude Opus 5 984d2e9d54 fix(e2e): spend a fresh TOTP counter per medewerker login (refs #132)
Keycloak refuses a TOTP code it has already accepted (otpPolicyCodeReusable
defaults to false), so the beheer specs — two serial logins as
bram-beheerder, well inside one 30-second window — sent the same code twice
and the second was rejected: the portal stayed on the OTP prompt and the
Catalogus heading never appeared. The Playwright retry ran inside the same
window too, so it failed identically.

loginMedewerker now spends the first counter the medewerker has left,
persisting it in tmpdir because Playwright restarts the worker process
between retries, and waits out the window when that counter is still ahead.

Verified against keycloak:26.1 with the real realm export: three
back-to-back logins as bram-beheerder now all succeed, where reusing one
code is refused with 401 invalid_grant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 09:55:58 +02:00
notandClaude Opus 5 716b8d03e0 test(e2e): a medewerker login must not reuse a spent TOTP counter (refs #132)
Two beheer specs log in as bram-beheerder back to back, so both submit the
code for the same 30-second counter. Keycloak's otpPolicyCodeReusable
defaults to false, so it refuses the second one as invalid credentials and
the beheer portal never loads — which is how verify-e2e went red on #158.

Pins the counter choice as a pure function of "now" and the last counter
this medewerker spent, so the guard is checkable without a browser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 09:53:55 +02:00
not a16f811d50 Merge branch 'main' into feat/132-medewerker-mfa
CI / build (pull_request) Successful in 1m3s
CI / lint (pull_request) Successful in 1m20s
CI / unit (pull_request) Successful in 1m24s
CI / frontend (pull_request) Successful in 3m11s
CI / mutation (pull_request) Successful in 6m17s
CI / verify-stack (pull_request) Failing after 9m38s
2026-09-04 06:47:59 +00:00
notandClaude Opus 5 a87a32e269 docs(infra): document MFA on the medewerker realm + ADR-0031 (refs #132)
CI / build (pull_request) Successful in 1m10s
CI / lint (pull_request) Successful in 1m27s
CI / unit (pull_request) Successful in 1m24s
CI / frontend (pull_request) Successful in 3m27s
CI / mutation (pull_request) Successful in 6m29s
CI / verify-stack (pull_request) Successful in 9m41s
Runbook gains an MFA section and how to get a code; synthetic-data lists the fixture
TOTP secret and the extra grant parameter; demo-script gains the S-15c note and its
staff logins now mention the second factor. check_realms.py grows an 'otp' argument
that prints a current code for a manual demo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 09:11:03 +02:00
notandClaude Opus 5 3567bc1f42 feat(infra): enforce MFA on the medewerker realm (refs #132)
Every seeded medewerker carries a TOTP credential, so Keycloak's conditional-OTP
step in both the browser and direct-grant flows always challenges them; a password
alone no longer yields a token. CONFIGURE_TOTP becomes a default required action so
any medewerker added later must enrol before logging in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 09:08:50 +02:00
notandClaude Opus 5 30c5279e90 test(infra): medewerker login must be refused without a second factor (refs #132)
The keycloak smoke check now asserts that a password-only grant on the medewerker
realm is rejected and that a TOTP code completes it. The e2e medewerker logins move
to a shared helper that submits Keycloak's OTP challenge. Both fail against the
current realm export, which enforces no MFA.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 09:07:45 +02:00