Portal SPA + dev stack: postgres + keycloak + browser OIDC#2
Merged
Conversation
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>
4 tasks
cjimti
added a commit
that referenced
this pull request
May 10, 2026
Portal SPA + dev stack: postgres + keycloak + browser OIDC
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.
Summary
This branch adds the M3 portal layer on top of the M1 endpoint-fixture core:
/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.pkg/httpsrv/browserauth.go) — full PKCE authorization-code flow against any OIDC issuer; ships a Keycloak realm indev/keycloak/api-test-realm.jsonwith 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).pkg/httpsrv/portal_api.go) at/api/v1/portal/*and/api/v1/admin/*—me,server(with secrets redacted),wellknown,endpoints,audit/*,dashboard,keysCRUD. Mutating routes gated by anX-Requested-WithCSRF check on top of session/API-key auth.make dev) — generates.env.devwith random secrets on first run, brings up Postgres + Keycloak in compose, builds the SPA if needed, runs the binary in foreground againstconfigs/api-test.live.yaml.make dev-anonis the no-Keycloak fast-iteration variant.PortalDeps—BuildMux(registry, readiness, mw, portalDeps)acceptsnilto keep the bare/v1/*+/healthzsurface 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;Identitycontext for browser-session callers. Intentionally separate frompkg/auth/inbound(which handles credentials the Plexara API gateway forwards) — the two domains share concepts but not types, withadaptInboundIdentityas the single approved bridge.BrowserRedirectmiddleware bounces apparent-browser GETs at/to/portal/; non-browser callers (curl, integration tests) still get the JSON banner.redactDatabaseURLcovers bothpostgres://user:pass@host/db?…URL form (via stdlib(*url.URL).Redacted()plus key-presence checks forpassword/sslpasswordquery params) and libpq DSN form (blanket-redacts on detect ofpassword=/sslpassword=with the full pgx separator class including\v— pgx accepts vertical-tab separators that Go's\sdoes not match).Verify pipeline
make verifynow also runsui-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 devbrings 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 fewcurlcalls 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 verifyis green locally and in CI.🤖 Generated with Claude Code