fix(keycloak): raise dev reset-credentials token lifespan to 30m (WS-05) - #276
Merged
Merged
Conversation
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 8, 2026
…WS-02 H3) (#280) Adds everything the prod techgarden realm needs EXCEPT the one line that turns it on. This is the only change in WS-02 touching a live shared IdP, so it is isolated: a broken hausparty or kian-coffee login is attributable to this PR instantly rather than bisected out of a large merge. THE GO SWITCH: base/realms/techgarden-realm.json exists but is deliberately absent from configMapGenerator.files. config-cli imports /config/*.json from that ConfigMap, so an unlisted file is never seen by Keycloak and https://sso.techgarden.gg/realms/techgarden keeps returning 404 — the pass condition for this phase, not a failure. The flip is that one line. Realm — DERIVED from the dev seed, not copied. Verified deltas vs dev, and nothing else drifted: 1. apex hostnames only — https://techgarden.gg/* and /oauth2/callback (ADR-0006: products are routes, not subdomains). No dev.techgarden.gg anywhere. 2. fromDisplayName "TechGarden", not "TechGarden (dev)" — a verbatim copy would put the wrong sender name on every production email. 3. registrationAllowed: false, declared not defaulted (ADR-0052). 4. all FOUR action-token lifespans carried (reset-credentials 1800, verify-email 43200, admin 259200, base 300). The base value alone would expire forgot-password links in 5 minutes — the bug dev shipped and fixed in #276. 5. THE FOUR DEV PERSONAS DROPPED. dev/power/casual/fresh are a dev fixture with out-of-band passwords; this IdP also fronts hausparty and kian.coffee. Prod gets the owner only. Also carried from WS-03: confidential client techgarden-gateway, revokeRefreshToken: true, 30-day idle / 60-day cap. And the `ai` realm role from #278, which landed after the brief was written (owner-only, held by kian). Theme — 17 files (11 login, 6 email), byte-identical to dev, mounted via spec.unsupported.podTemplate at /opt/keycloak/themes/techgarden/. TWO ConfigMaps, not one: configMapGenerator keys off each file's base name and ConfigMap keys cannot contain "/", so theme.properties and messages_en.properties collide across login/ and email/. theme-revision starts at "1" — prod's counter is independent of dev's "2" and the two must never be synced up. Nothing references the theme until the realm is enabled. Substitution wiring — the sharpest edge. IMPORT_VARSUBSTITUTION_ENABLED is true over /config/*.json, so a seed referencing an unwired variable fails substitution for EVERY realm file in the batch at once. KC_CLIENT_TECHGARDEN_GATEWAY_SECRET and KC_SMTP_PASSWORD are therefore wired NOW, alongside the two new ExternalSecret keys, so the flip cannot take hausparty and kian-coffee down with it. Resend: prod SHARES the dev sender. The free tier allows one verified domain and there is exactly one `resend` entry in the BWS org — so both environments share the 3,000/month cap and ADR-0052's fail-closed-on-429 becomes a shared property. Claude-Session: https://claude.ai/code/session_015tcbrNifvkc8ryk4g2M7XK
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.
Follow-up to #275, raised by the WS-05 orchestrator during stage 3 joint validation.
The bug
Keycloak resolves the reset-credentials action token from the base
actionTokenGeneratedByUserLifespan. #275 pinned that at300and overrode onlyverify-email, so forgot-password links in dev expire in 5 minutes — on the exact flow WS-04 was built to provide.The fix
30 minutes. Per-action override rather than raising the base, so
verify-emailkeeps its 12h and any future user-initiated action still inherits Keycloak's conservative 300s unless deliberately overridden.Why 300 was wrong
It was a deliberate choice in #275 — but reasoned against
43200as the only alternative, on the grounds that a 144× wider credential-reset window buys nothing. That's true and irrelevant: a middle value was never considered. 1800 is 6×, sits in the normal band for password-reset tokens, and makes the flow usable.Scope
Realm-only. Reconciled by the existing
keycloak-config-cliPostSync hook via the Admin API — no theme bytes touched, so notheme-revisionbump and no pod roll.Resulting dev lifespans:
execute-actions-email(invite)actionTokenGeneratedByAdminLifespanValidation
python3 -m json.toolon the realm seed — validkustomize build kubernetes/clusters/1276-dev/keycloak/keycloak— pass🤖 Generated with Claude Code
https://claude.ai/code/session_01S5ph2xAnD7oCR8YmFmobDn