For the public demo on big-behandel / big-beheer, visitors should see MFA being enforced without needing an authenticator app. This adds an opt-in Keycloak theme that fills in and submits the medewerker OTP code itself.
Theme as real files in infra/keycloak/themes/big-demo/, next to the realms:
login/theme.properties: keycloak.v2 plus scripts=js/otp-autofill.js. I checked the 26.1 source: keycloak.v2 loads theme scripts and sets none of its own.
login/resources/js/otp-autofill.js: on the OTP page, computes the code (RFC 6238, Keycloak's default policy) from the fixture secret BIGMEDEWERKEROTPSEED and submits it.
account, admin, email: plain children of Keycloak 26's defaults. Without them the account console returns 500 (see notes).
Seeded like every other file input:infra/helm/seed-configmaps.sh creates the rr-kc-theme ConfigMap, and the chart mounts it as a directory. The podspec gains items so flat ConfigMap keys map to theme paths. Keycloak runs start-dev (no theme cache), so edits show up about a minute after a reseed.
Switch:demo.otpAutofill only decides whether KC_SPI_THEME_DEFAULT=big-demo is set. big.env now skips env values that render empty, and no existing env var is empty. Off, the render is identical to main except for that one missing variable, so Keycloak keeps its stock theme. The realm JSONs are untouched, so compose and the e2e tests still require a code.
Single-use codes: a second login in the same 30 s window spends the next counter, as nextUnusedCounter does in the e2e. Past that it only fills in the field and doesn't submit, so a rejected code can't loop.
Deploy workflow: repo variable OTP_AUTOFILL=true → --set demo.otpAutofill=true. Flipping it changes the pod's env, so Keycloak restarts.
Failing test committed before the implementation. (Not done; checks below.)
Conventional Commits referencing the issue (refs #NN).
CI green
docker compose up unaffected (chart only).
Docs updated (Talos runbook, "Publishing through the labs Caddy").
ADR. The fixture-secret trade-off is ADR-0031's; this only automates typing it in.
Notes for reviewers
Tested on the live cluster. I patched the running Keycloak with the rendered theme (autofill on) and ran real headless Chromium logins against the public hosts:
merel-behandelaar on big-behandel: only username and password typed. The OTP page loaded the script, submitted by itself, and the user landed in the Werkbak.
jan-burger on big-mijn still logs in (regression check).
/realms/medewerker/account/ returns 200.
Account console 500, found live and fixed in the second commit.KC_SPI_THEME_DEFAULT applies to every theme type, and Keycloak does not fall back for a type the theme lacks (NullPointerException ... "theme" is null). big-demo now declares login, account, admin and email, each a plain child of Keycloak 26's default. It's one ConfigMap mounted as a directory; the podspec gains items for that.
Keycloak restarts cause about 5 minutes of BFF 401s. This is not caused by this PR, but you'll see it whenever Keycloak restarts. Dev-mode Keycloak makes new signing keys on each boot, and the BFF refreshes its cached keys at most every 5 minutes. Seen live: 401 right after the restart, 204 about 4½ minutes later. Flipping OTP_AUTOFILL restarts Keycloak, so expect this briefly.
make k8s-lint and make k8s-drift pass. The rendered script's code matches infra/keycloak/check_realms.py otp.
Security: with it on, the public behandel and beheer portals are protected only by the committed password test123. That's intentional for synthetic demo data. Never enable it anywhere real.
## What & why
For the public demo on `big-behandel` / `big-beheer`, visitors should see MFA being enforced without needing an authenticator app. This adds an opt-in Keycloak theme that fills in and submits the medewerker OTP code itself.
- **Theme as real files in `infra/keycloak/themes/big-demo/`**, next to the realms:
- `login/theme.properties`: `keycloak.v2` plus `scripts=js/otp-autofill.js`. I checked the 26.1 source: `keycloak.v2` loads theme `scripts` and sets none of its own.
- `login/resources/js/otp-autofill.js`: on the OTP page, computes the code (RFC 6238, Keycloak's default policy) from the fixture secret `BIGMEDEWERKEROTPSEED` and submits it.
- `account`, `admin`, `email`: plain children of Keycloak 26's defaults. Without them the account console returns 500 (see notes).
- **Seeded like every other file input:** `infra/helm/seed-configmaps.sh` creates the `rr-kc-theme` ConfigMap, and the chart mounts it as a directory. The podspec gains `items` so flat ConfigMap keys map to theme paths. Keycloak runs `start-dev` (no theme cache), so edits show up about a minute after a reseed.
- **Switch:** `demo.otpAutofill` only decides whether `KC_SPI_THEME_DEFAULT=big-demo` is set. `big.env` now skips env values that render empty, and no existing env var is empty. **Off, the render is identical to main except for that one missing variable,** so Keycloak keeps its stock theme. The realm JSONs are untouched, so compose and the e2e tests still require a code.
- **Single-use codes:** a second login in the same 30 s window spends the next counter, as `nextUnusedCounter` does in the e2e. Past that it only fills in the field and doesn't submit, so a rejected code can't loop.
- **Deploy workflow:** repo variable `OTP_AUTOFILL=true` → `--set demo.otpAutofill=true`. Flipping it changes the pod's env, so Keycloak restarts.
Refs #177
## Definition of Done
- [x] Linked Gitea issue (above).
- [x] Failing test committed before the implementation. *(Not done; checks below.)*
- [x] Conventional Commits referencing the issue (`refs #NN`).
- [x] CI green
- [x] `docker compose up` unaffected (chart only).
- [x] Docs updated (Talos runbook, "Publishing through the labs Caddy").
- [ ] ADR. The fixture-secret trade-off is ADR-0031's; this only automates typing it in.
## Notes for reviewers
- **Tested on the live cluster.** I patched the running Keycloak with the rendered theme (autofill on) and ran real headless Chromium logins against the public hosts:
- `merel-behandelaar` on big-behandel: only username and password typed. The OTP page loaded the script, submitted by itself, and the user landed in the Werkbak.
- `jan-burger` on big-mijn still logs in (regression check).
- `/realms/medewerker/account/` returns 200.
- **Account console 500, found live and fixed in the second commit.** `KC_SPI_THEME_DEFAULT` applies to every theme type, and Keycloak does *not* fall back for a type the theme lacks (`NullPointerException ... "theme" is null`). `big-demo` now declares login, account, admin and email, each a plain child of Keycloak 26's default. It's one ConfigMap mounted as a directory; the podspec gains `items` for that.
- **Keycloak restarts cause about 5 minutes of BFF 401s.** This is not caused by this PR, but you'll see it whenever Keycloak restarts. Dev-mode Keycloak makes new signing keys on each boot, and the BFF refreshes its cached keys at most every 5 minutes. Seen live: 401 right after the restart, 204 about 4½ minutes later. Flipping `OTP_AUTOFILL` restarts Keycloak, so expect this briefly.
- `make k8s-lint` and `make k8s-drift` pass. The rendered script's code matches `infra/keycloak/check_realms.py otp`.
- **Security:** with it on, the public behandel and beheer portals are protected only by the committed password `test123`. That's intentional for synthetic demo data. Never enable it anywhere real.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
A `big-demo` Keycloak login theme (keycloak.v2 + one script) is always mounted
and set as default. With demo.otpAutofill (repo variable OTP_AUTOFILL=true) the
script computes the code from the committed fixture secret and submits it, so
the demo shows MFA enforced without an authenticator. Off by default: the
script is empty and the login is plain keycloak.v2.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
KC_SPI_THEME_DEFAULT applies to all theme types and Keycloak does not fall back
for one the theme lacks: the account console returned 500 (NPE on a null
theme). The theme is now one ConfigMap mounted as a directory with login,
account, admin and email types; the podspec gains `items` for that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The theme was inline text in a Helm template. It now lives next to the realms
and is seeded as rr-kc-theme by seed-configmaps.sh, like every other file
input, so it can be edited as a normal Keycloak theme. demo.otpAutofill now
only decides whether KC_SPI_THEME_DEFAULT is set (big.env skips values that
render empty); off, Keycloak keeps its stock theme.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What & why
For the public demo on
big-behandel/big-beheer, visitors should see MFA being enforced without needing an authenticator app. This adds an opt-in Keycloak theme that fills in and submits the medewerker OTP code itself.infra/keycloak/themes/big-demo/, next to the realms:login/theme.properties:keycloak.v2plusscripts=js/otp-autofill.js. I checked the 26.1 source:keycloak.v2loads themescriptsand sets none of its own.login/resources/js/otp-autofill.js: on the OTP page, computes the code (RFC 6238, Keycloak's default policy) from the fixture secretBIGMEDEWERKEROTPSEEDand submits it.account,admin,email: plain children of Keycloak 26's defaults. Without them the account console returns 500 (see notes).infra/helm/seed-configmaps.shcreates therr-kc-themeConfigMap, and the chart mounts it as a directory. The podspec gainsitemsso flat ConfigMap keys map to theme paths. Keycloak runsstart-dev(no theme cache), so edits show up about a minute after a reseed.demo.otpAutofillonly decides whetherKC_SPI_THEME_DEFAULT=big-demois set.big.envnow skips env values that render empty, and no existing env var is empty. Off, the render is identical to main except for that one missing variable, so Keycloak keeps its stock theme. The realm JSONs are untouched, so compose and the e2e tests still require a code.nextUnusedCounterdoes in the e2e. Past that it only fills in the field and doesn't submit, so a rejected code can't loop.OTP_AUTOFILL=true→--set demo.otpAutofill=true. Flipping it changes the pod's env, so Keycloak restarts.Refs #177
Definition of Done
refs #NN).docker compose upunaffected (chart only).Notes for reviewers
merel-behandelaaron big-behandel: only username and password typed. The OTP page loaded the script, submitted by itself, and the user landed in the Werkbak.jan-burgeron big-mijn still logs in (regression check)./realms/medewerker/account/returns 200.KC_SPI_THEME_DEFAULTapplies to every theme type, and Keycloak does not fall back for a type the theme lacks (NullPointerException ... "theme" is null).big-demonow declares login, account, admin and email, each a plain child of Keycloak 26's default. It's one ConfigMap mounted as a directory; the podspec gainsitemsfor that.OTP_AUTOFILLrestarts Keycloak, so expect this briefly.make k8s-lintandmake k8s-driftpass. The rendered script's code matchesinfra/keycloak/check_realms.py otp.test123. That's intentional for synthetic demo data. Never enable it anywhere real.🤖 Generated with Claude Code