feat(keycloak): techgarden login + email theme on dev (WS-05) - #275
Merged
Conversation
Theme tree taken verbatim from TechGardenCode/techgarden@79b6b81, docs/cross-repo/ws-05-theme/ — 18 files, 265,124 B, all verified byte-identical by git blob SHA. Authored and proven app-side against quay.io/keycloak/keycloak:26.6.3 (six login surfaces driven in a browser light+dark, three emails inspected, zero outbound requests, a11y measured). This is the theme's living home; the app-repo copy is transit only and is deleted at WS-05 stage 3. Do not hand-edit login.css, theme.properties or the message bundles — every value is copied from the app repo's design-system/, which carries a drift callback here. Wiring lands in the next commit. Refs: WS-05, TechGardenCode/techgarden#181 Claude-Session: https://claude.ai/code/session_01S5ph2xAnD7oCR8YmFmobDn
Mounts the vendored theme into the operator-managed Keycloak and turns
it on for the dev techgarden realm. Dev only — prod has no techgarden
realm; WS-02 derives the prod seed from this.
kustomization.yaml
Two configMapGenerator entries, not one. configMapGenerator keys off
the file's BASE name and ConfigMap keys cannot contain "/", so a
single ConfigMap collides: theme.properties and
messages_en.properties each exist under both login/ and email/.
Splitting per theme type removes the collision without renaming keys.
woff2/ico/png land in binaryData automatically; the login ConfigMap
serializes to 336 KB (32% of the 1 MiB cap), email to 13 KB.
base/keycloak-cr.yaml
spec.unsupported.podTemplate mounts both ConfigMaps at
/opt/keycloak/themes/techgarden/{login,email}, with items[] rebuilding
the subdirectories configMapGenerator flattened. KeycloakSpec @26.6.3
exposes no supported volume field, so this Tech Preview escape hatch
is the only ConfigMap route; accepted because the operator is
version-pinned and vendored in base/operator/. Verified in the running
pod that /opt/keycloak/themes/ holds only README.md, so the mount
shadows nothing and parent=base keeps resolving.
Adds a techgarden.gg/theme-revision annotation. Keycloak caches themes
for 30 days in production mode and the operator does not watch these
ConfigMaps, so theme bytes are not served until the pod rolls — and
the ConfigMaps have stable names, so nothing else forces one. Bump the
annotation on any change under base/themes/.
base/realms/techgarden-realm.json
loginTheme + emailTheme = techgarden.
Also pins the action-token lifespan. The invite path was already fine:
actionTokenGeneratedByAdminLifespan is 259200 (72h), so admin-issued
execute-actions-email links were never the 5-minute ones. The 5 minutes
observed app-side is actionTokenGeneratedByUserLifespan (Keycloak
default 300s), which drives self-service verify-email AND
forgot-password. Pinned at 300 with a verify-email-only override to
43200 (12h) via realm attributes, so a friend's verify link survives
the wait without widening the credential-reset window 144x. Live realm
attributes were empty, so this is purely additive.
smtpServer / Resend transport untouched — emailTheme changes templates,
not transport.
Refs: WS-05, TechGardenCode/techgarden#181
Claude-Session: https://claude.ai/code/session_01S5ph2xAnD7oCR8YmFmobDn
TechGardenCode
added a commit
that referenced
this pull request
Aug 5, 2026
…05) (#276) Forgot-password links in the dev techgarden realm expired in 5 minutes. Keycloak resolves the reset-credentials token from the base actionTokenGeneratedByUserLifespan, which #275 pinned at 300 — so the verify-email override landed but the reset flow kept the default. That 300 was a decision, but a badly framed one: it was chosen against 43200 as the only alternative, on the grounds that a 144x wider window for credential reset buys nothing. A middle value was never considered. 1800 is 6x, sits in the normal band for password-reset tokens, and makes the flow WS-04 exists to provide actually usable — request a reset, go find your phone, open the mail six minutes later, and the link still works. Per-action override rather than raising the base, so verify-email keeps 12h and any future user-initiated action still inherits Keycloak's conservative 300s unless deliberately overridden. Realm-only change: reconciled by the config-cli PostSync hook via the Admin API. No theme bytes touched, so no theme-revision bump and no pod roll needed. Reported by the WS-05 orchestrator during stage 3 joint validation. Refs: WS-05, TechGardenCode/techgarden#181, #275 Claude-Session: https://claude.ai/code/session_01S5ph2xAnD7oCR8YmFmobDn
TechGardenCode
added a commit
that referenced
this pull request
Aug 5, 2026
…277) The forgot-password success alert was still stock Keycloak. Found by the orchestrator during stage-3 live validation, not reachable locally: info.ftl never renders in that flow — Keycloak returns the same response whether or not the account exists, as an alert back on the sign-in page. login/messages/messages_en.properties copied verbatim from the app repo at 24af3ca8 (blob d7fc75a). One key added, emailSentMessage, plus its comment; the wording still does not confirm the account exists, so the anti-enumeration property is preserved. Diffed the whole tree: the other 17 files are byte-identical, so nothing else moved. Bumps techgarden.gg/theme-revision 1 -> 2. The theme ConfigMaps have stable names and Keycloak caches themes for 30 days in production mode, so without this the ConfigMap would update and the old bytes would keep being served. Refs: WS-05, TechGardenCode/techgarden#181, #275 Claude-Session: https://claude.ai/code/session_01S5ph2xAnD7oCR8YmFmobDn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Infra half of cross-repo workstream WS-05. App half: TechGardenCode/techgarden#181 (
feature/ws-05-keycloak-login-theme), brief atdocs/cross-repo/ws-05-keycloak-login-theme.md.Dev only. Prod has no
techgardenrealm; WS-02 derives the prod seed from this.Route taken: ConfigMap (as briefed), with one contract delta
Delta — two ConfigMaps, not one. The brief specified a single ConfigMap with 18
items[]. That isn't expressible:configMapGeneratorkeys off the file's base name and ConfigMap keys can't contain/, and the tree has two basename collisions —theme.properties(in bothlogin/andemail/) andmessages_en.properties(in bothlogin/messages/andemail/messages/). Splitting per theme type removes the collision without key renaming, and dropsitems[]to 11 + 6 = 17. The 18th file isREADME.md— docs, lives beside the theme, not mounted.Zero theme bytes changed. All 18 files verified byte-identical to
TechGardenCode/techgarden@79b6b81by git blob SHA.Theme location
The three confirmations
spec.unsupported.podTemplate— accepted. Confirmed present in the vendoredbase/operator/crd-keycloaks.yaml@26.6.3, and confirmed as the only volume route. Tech Preview is tolerable here because the operator is version-pinned and vendored in-repo, so merge semantics can't shift under an unplanned upgrade. The custom-image fallback is rejected for the brief's reasons plus one it didn't have: homelab image tags are bot-managed viaci/app-registry.yamland a PreToolUse hook blocks hand edits, so a hand-built Keycloak image needs a publish path that doesn't exist.binaryDataauto-selected for the 4 woff2 +.ico+.png; the 2 OFL licences andlogin.cssstayed UTF-8 indata.generatorOptions.disableNameSuffixHash: trueis global in this kustomization (config-cli mounts the realm CM by fixed name), so theme CMs get stable names and a theme edit alone won't roll the pod. And Keycloak caches themes 30 days in production mode, so a roll is required regardless of naming. Addedtechgarden.gg/theme-revisionon the podTemplate: bump it on any change underbase/themes/and ArgoCD delivers the roll — no out-of-bandkubectl. This PR's own rollout is forced by the volume change itself.Action-token lifespan
The brief pointed at the wrong field.
actionTokenGeneratedByAdminLifespanwas already 259200 (72h), so admin-issuedexecute-actions-emailinvites were never the 5-minute ones. The 5 minutes observed app-side isactionTokenGeneratedByUserLifespan(Keycloak default 300s), which drives self-service verify-email and forgot-password.Settled on: pin the base at
300, override verify-email only to43200(12h) via realm attributes. A friend's verify link survives the wait; the credential-reset window doesn't widen 144×. Live realmattributeswas{}, so the addition is purely additive.smtpServer/ WS-04 Resend transport untouched.Validation
kustomize build kubernetes/clusters/1276-dev/keycloak/keycloak— passkubectl apply --server-side --dry-run=serveragainst the live dev CRD — pass (CR + both ConfigMaps)python3 -m json.toolon the realm seed — valid/opt/keycloak/themes/contains onlyREADME.md, so the mount shadows nothing andparent=basekeeps resolvingkeycloak, 2 mounts, 11 + 6items[],binaryDataon the 6 binariesbase/realms/andbase/operator/already exist and generate nonePost-merge:
config-cliPostSync applies the realm fields, and the podTemplate change rollskeycloak-0.🤖 Generated with Claude Code
https://claude.ai/code/session_01S5ph2xAnD7oCR8YmFmobDn