v14.2.0
Minor bump. Phase DD lays down the OIDC code-flow login wiring on top
of DC's HTTPS surface. New capability — recon-gen studio /
recon-gen dashboards can gate every request through any
OIDC-compliant provider (Okta / Auth0 / AzureAD / Keycloak / Dex)
with zero per-tenant code, and the runner spins a real Dex container
for test/CI parity with the production posture.
What's new
src/recon_gen/common/html/auth.py—JwtCookieMiddleware
decodes therecon_gen_sessionHMAC-SHA256 JWT on every request,
branches onHX-Requestto return 401 JSON vs 302 →/auth/login.
Public-prefix bypass("/auth/", "/static/", "/docs/", "/health").
oauth_routes(...)builds the three/auth/{login,callback,logout}
routes via authlib'sstarlette_client.OAuth. Logout follows
RP-Initiated Logout via the IdP'send_session_endpointdiscovery.make_app(cfg=...)— auth wiring fires only when
cfg.auth.oidcANDcfg.auth.sessionare both set. Absent
block ⇒ HTTP-local-dev passthrough (no behavior change for existing
deployments). Newitsdangerous>=2.2runtime dep (SessionMiddleware
for authlib's PKCE/state round-trip storage).src/recon_gen/_dev/oidc/— 5-module Dex coordinator
(secrets / container / config_writer / ensure / init). Spins or
adopts the sharedrecon-gen-test-dexDocker container, mounts the
DC.3-managed LE cert for HTTPS, injects per-run scrambled
client_secret+ bcrypt-hashed user password via Dex's
secretEnv/hashFromEnvyaml fields. Issuer URLs locked at
https://localdev.recon-gen.hotchkiss.io:5557/dex(DEV) /
https://localci.recon-gen.hotchkiss.io:5556/dex(CI). Excluded
from the published wheel; the Dex container + bcrypt deps live
under the[dev]extra only.OIDC_TOUCHING_LAYERS = ("app2",)+_ensure_oidc_if_configured
pre-flight incmd_up_to. Narrower than TLS —qs_browseruses
auth-independent QS embed URLs. Hard-depends on DC.3; returns
EXIT_NEEDS_OPERATORwithtls-setup.mdhint when
cfg.auth.oidcis set butcfg.app2.tlsisNone.- DashboardDriver OIDC verbs —
sign_in_via_oidc(*, email, password)/
sign_out_via_oidc()/
inspect_jwt_cookie(). App2 drives Dex v2.40.0's
password.html+approval.htmlform selectors.QsEmbedDriver
raisesNotImplementedErrorfor all three under a structured
triple (raise-site comment cross-linking
[[project_qs_embed_url_presigned_no_oidc]]+
docs/reference/quicksight-quirks.mdentry + memory file) — QS
embed URLs are pre-signed at mint time so OIDC verbs have no QS
analog. docs/operations/oidc-setup.md— full operator runbook:
three postures (HTTP local-dev / production OIDC / managed Dex),
cfg block + field semantics, secrets convention
(run/secrets.envcontinuation), IdP-side client registration
recipe, CI GitHub-secrets recipe, eight troubleshooting recipes,
rotation procedure..github/workflows/ci.yml—Generate random OIDC credentials
step + cfg-overwrite heredoc carryingauth.oidc:+auth.session:
blocks. Three new GitHub secrets surface (OIDC_CLIENT_SECRET/
JWT_SECRET/DEX_USER_PASSWORD) but the workflow'sopenssl rand
step generates fresh per-run values today; the secrets are
configured for future pinning.tests/e2e/app2/test_oauth_login_flow.py— 7-test browser e2e
pinning the JwtCookieMiddleware contract through Playwright
(unauth 302 / HX-Request 401 / valid JWT renders / cookie
inspection / sign-in idempotency / sign-out clears cookie /
tampered JWT 401). Skips whencfg.auth.oidc/cfg.auth.session
absent (cfg-shape skip — POLICY 1 consistent across CI / local).
Env-var surface
Four new RECON_GEN_* env vars register with the typed registry:
RECON_GEN_OIDC_CLIENT_SECRET— OIDC client secret (named via
cfg.auth.oidc.client_secret_env).RECON_GEN_JWT_SECRET— local session JWT HMAC signing key (named
viacfg.auth.session.jwt_secret_env).RECON_GEN_DEX_USER_PASSWORD— plaintext password for the static
Dex test user (testuser@example.com).RECON_GEN_DEX_URL— short-circuit for the pre-spun shared-Dex
pattern (registered, not yet wired intoci.yml; reserved for the
future shared-CI-Dex step).
No breaking changes
Existing deployments with no auth: block in their cfg yaml see
zero behavior change — the middleware short-circuits to passthrough
and the new env-var registrations are inert. To opt in: add the two
blocks per docs/operations/oidc-setup.md Step 2.
Carry-along fixes
- Date-picker value-commit needs Tab, not Enter
(docs/reference/quicksight-quirks.mdAA.A.l2ft-date-commit). QS's
ParameterDateTimePickerReactonChangewiring listens for
focus-loss, not Enter — same root cause as the σ-slider
commit-needs-Tab pattern already documented inline. Surfaced by
thetest_l2ft_additive_pickers_keep_anchor_row[qs-Rails]triage
(5,551 rows post-filter vs the expected ≤9). Backlog #83 carries
the typed-wrapper follow-up. - Bulk-insert helpers —
bulk_insert_tx/bulk_insert_balance
gain an optionalcolumnsparameter so ETL integrators can
bulk-load schema columns the spine-author defaults exclude
(transfer_completion/bundle_idfor transactions;
supersedesfor balances)._coerce_to_cents_intnow accepts
str(CSV bulk loads —csv.DictReaderlands every column as a
string) and raisesTypeErroron unknown types instead of silent
passthrough that surfaced as opaque downstream BIGINT INSERT
failures.