Skip to content

Portal SPA + dev stack: postgres + keycloak + browser OIDC#2

Merged
cjimti merged 1 commit into
mainfrom
portal-and-dev-stack
May 10, 2026
Merged

Portal SPA + dev stack: postgres + keycloak + browser OIDC#2
cjimti merged 1 commit into
mainfrom
portal-and-dev-stack

Conversation

@cjimti
Copy link
Copy Markdown
Contributor

@cjimti cjimti commented May 10, 2026

Summary

This branch adds the M3 portal layer on top of the M1 endpoint-fixture core:

  • React/Vite SPA at /portal/ — Dashboard, Endpoints catalog, Audit explorer with detail drawer, API Keys (DB-backed, when enabled), Config (with secrets redacted), About. Embedded into the Go binary via //go:embed.
  • Browser OIDC login (pkg/httpsrv/browserauth.go) — full PKCE authorization-code flow against any OIDC issuer; ships a Keycloak realm in dev/keycloak/api-test-realm.json with the three clients the Plexara gateway tests need (api-test-portal, plexara-cc, plexara-ac). Session persisted in an HMAC-signed cookie (pkg/httpsrv/session.go).
  • Portal API (pkg/httpsrv/portal_api.go) at /api/v1/portal/* and /api/v1/admin/*me, server (with secrets redacted), wellknown, endpoints, audit/*, dashboard, keys CRUD. Mutating routes gated by an X-Requested-With CSRF check on top of session/API-key auth.
  • One-command dev stack (make dev) — generates .env.dev with random secrets on first run, brings up Postgres + Keycloak in compose, builds the SPA if needed, runs the binary in foreground against configs/api-test.live.yaml. make dev-anon is the no-Keycloak fast-iteration variant.
  • Optional PortalDepsBuildMux(registry, readiness, mw, portalDeps) accepts nil to keep the bare /v1/* + /healthz surface unchanged when the portal is disabled, so existing deployments aren't forced to opt in.

Architecture notes

  • pkg/auth (new) — OIDC JWT validator with JWKS cache, singleflight refresh, and stale-while-revalidate; Identity context for browser-session callers. Intentionally separate from pkg/auth/inbound (which handles credentials the Plexara API gateway forwards) — the two domains share concepts but not types, with adaptInboundIdentity as the single approved bridge.
  • BrowserRedirect middleware bounces apparent-browser GETs at / to /portal/; non-browser callers (curl, integration tests) still get the JSON banner.
  • redactDatabaseURL covers both postgres://user:pass@host/db?… URL form (via stdlib (*url.URL).Redacted() plus key-presence checks for password/sslpassword query params) and libpq DSN form (blanket-redacts on detect of password=/sslpassword= with the full pgx separator class including \v — pgx accepts vertical-tab separators that Go's \s does not match).

Verify pipeline

make verify now also runs ui-verify (TypeScript + Vite build of the SPA, mirrored from CI's frontend job). Coverage gate is at 84% across the gated packages; new tests cover OIDC validation (with a fake-IdP fixture), session encode/decode, PKCE login + callback round-trip, CSRF gate, SPA fallback routing, secret redaction across all six secret-bearing config fields, and the dozen-or-so DSN/URL edge cases that pgx accepts.

Test plan

  • make dev brings up Postgres + Keycloak + the binary; http://localhost:8080/portal/ redirects to login, dev/dev signs in, the Dashboard shows recent activity once you fire a few curl calls at /v1/*.
  • make dev-anon (Postgres only) lets /v1/* calls land in the audit log without needing OIDC.
  • curl http://localhost:8080/api/v1/portal/server -H \"X-API-Key: \$APITEST_DEV_KEY\" returns a config blob with [redacted] in every secret-bearing field (cookie secret, OIDC client secret, Plexara admin auth header, file API keys, bearer tokens, database URL).
  • make verify is green locally and in CI.

🤖 Generated with Claude Code

Wires a React/Vite portal at /portal/ backed by browser-flow OIDC against a
local Keycloak realm, plus the docker-compose dev stack and the Makefile
targets (`make dev`, `make dev-anon`, `make ui`) that bring it up. The mux
now takes an optional PortalDeps bundle so the bare /v1/* + /healthz surface
still builds when the portal is disabled.

Surfaces added under pkg/httpsrv: PKCE login/callback (browserauth), session
cookie (HMAC-signed), portal API (me/server/wellknown/dashboard/endpoints/
audit/keys), CSRF header gate on writes, SPA serving with client-route
fallback, RFC 9728 protected-resource metadata. pkg/auth holds the OIDC
JWT validator (JWKS cache + singleflight + stale-while-revalidate) and the
portal-side Identity context separate from pkg/auth/inbound.

sanitizedConfig redacts portal/oidc/bearer/api-key/plexara-admin secrets and
the database URL/DSN before the Config page sees it; redactDatabaseURL
covers both URL form (via stdlib (*url.URL).Redacted() + key-presence check
on `password`/`sslpassword` query params) and DSN form (blanket redaction
on detect of `password=`/`sslpassword=` with the full pgx separator class
including `\v`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cjimti cjimti merged commit 78b5e66 into main May 10, 2026
7 checks passed
@cjimti cjimti deleted the portal-and-dev-stack branch May 10, 2026 19:42
cjimti added a commit that referenced this pull request May 10, 2026
Portal SPA + dev stack: postgres + keycloak + browser OIDC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant