CI / lint (pull_request) Successful in 2m0s
CI / k8s (pull_request) Successful in 9s
CI / build (pull_request) Successful in 1m22s
CI / unit (pull_request) Successful in 1m34s
CI / verify-stack (pull_request) Canceled after 0s
CI / frontend (pull_request) Canceled after 3m8s
CI / mutation (pull_request) Canceled after 8m10s
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>
63 lines
2.7 KiB
YAML
63 lines
2.7 KiB
YAML
{{- /*
|
|
Keycloak theme `big-demo`: keycloak.v2 plus one login script. It is always
|
|
mounted and always the default theme (KC_SPI_THEME_DEFAULT), so the only thing
|
|
`demo.otpAutofill` switches is what that script does. Off, it is empty and the
|
|
login is exactly keycloak.v2.
|
|
|
|
KC_SPI_THEME_DEFAULT covers every theme type, and Keycloak does not fall back
|
|
for a type the theme lacks (the account console 500s), so account, admin and
|
|
email are declared too, each a plain child of Keycloak 26's own default.
|
|
|
|
On, the medewerker OTP step computes the code from the realm fixture secret
|
|
(docs/runbooks/keycloak.md) and submits it: the demo still shows MFA being
|
|
enforced without anyone needing an authenticator. The secret is committed and
|
|
shared, so this is a demo convenience only — never enable it anywhere real.
|
|
*/ -}}
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: kc-theme
|
|
labels:
|
|
{{- include "big.labels" (dict "root" $ "name" "kc-theme") | nindent 4 }}
|
|
data:
|
|
login.properties: |
|
|
parent=keycloak.v2
|
|
import=common/keycloak
|
|
scripts=js/otp-autofill.js
|
|
account.properties: |
|
|
parent=keycloak.v3
|
|
admin.properties: |
|
|
parent=keycloak.v2
|
|
email.properties: |
|
|
parent=keycloak
|
|
otp-autofill.js: |
|
|
{{- if .Values.demo.otpAutofill }}
|
|
// RFC 6238 with Keycloak's default policy (HmacSHA1, 6 digits, 30 s) over the
|
|
// raw bytes of the medewerker fixture secret — same as tests/e2e/keycloak-login.ts.
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
const input = document.querySelector('input[name="otp"]');
|
|
if (!input || !input.form) return;
|
|
const key = await crypto.subtle.importKey('raw',
|
|
new TextEncoder().encode('BIGMEDEWERKEROTPSEED'), { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);
|
|
// A code is single-use, so a second login in the same window spends the next
|
|
// counter (Keycloak's look-ahead accepts it). Past that, fill but don't submit,
|
|
// so a rejected code can't turn into a submit loop.
|
|
const now = Math.floor(Date.now() / 30000);
|
|
let last = -1;
|
|
try { last = Number(sessionStorage.getItem('big-otp-counter')) || -1; } catch {}
|
|
const counter = Math.max(now, last + 1);
|
|
const msg = new DataView(new ArrayBuffer(8));
|
|
msg.setBigUint64(0, BigInt(counter));
|
|
const mac = new Uint8Array(await crypto.subtle.sign('HMAC', key, msg.buffer));
|
|
const o = mac[19] & 0x0f;
|
|
const n = ((mac[o] & 0x7f) << 24 | mac[o + 1] << 16 | mac[o + 2] << 8 | mac[o + 3]) % 1e6;
|
|
input.value = String(n).padStart(6, '0');
|
|
if (counter > now + 1) return;
|
|
try { sessionStorage.setItem('big-otp-counter', String(counter)); } catch {}
|
|
input.form.requestSubmit();
|
|
});
|
|
{{- else }}
|
|
// demo.otpAutofill is off: the OTP step is entered by hand.
|
|
{{- end }}
|