From 17b400a684fb326c68311775c9430cc582eaeefb Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Mon, 11 May 2026 11:52:26 +0000 Subject: [PATCH] feat(gui/oidc): Test Connection panel on create + edit forms (MED-5 GUI half) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit 2026-05-11 Fix 09 closure. MED-5's backend dry-run endpoint (POST /api/v1/auth/oidc/test, gated auth.oidc.create) shipped on dev/auth-bundle-2 (commit 00bbef7) but the GUI never called it — authOIDCTestProvider in web/src/api/client.ts was dead code. Operator gap before this fix: complete the create form blind, save, then click 'Refresh' to discover whether the issuer URL worked. Discovery failures left a broken provider row in the DB that had to be deleted before retrying. The MED-5 backend exists to short- circuit this — surface the dry-run result before commit. New shared component web/src/pages/auth/OIDCTestConnectionPanel.tsx calls authOIDCTestProvider against the live form state (issuer URL + client ID + parsed scopes) and renders a four-row status panel inline: * ✓/✗ Discovery fetched (with issuer-echo from the well-known doc) * ✓/✗ JWKS reachable (with the discovered jwks_uri) * ✓/⚠ Supported algs (warning glyph when the IdP advertises none — distinct from a discovery failure) * ✓/· RFC 9207 iss-parameter advertised (informational · glyph rather than ✗ because the spec is SHOULD, not MUST) Backend per-leg errors[] flow into an inline bullet list. A top-level rectangle catches network/fetch failures separately. The Run button is disabled when the issuer URL is empty or whitespace-only. The component does NOT persist anything — safe to run repeatedly before the operator clicks Save. The panel is mounted in two places: * OIDCProvidersPage create modal (between the form fields and the Create button) — short-circuits the blind-save footgun for new provider configs. * OIDCProviderDetailPage edit form (between the field grid and the Save button) — load-bearing for verifying IdP rotations (Keycloak realm rename, Okta tenant move, certctl side-by-side hostname change) without committing first. A testIDSuffix prop (default 'create' / 'edit') gives each mount point a distinct data-testid namespace so both panels can coexist on a hypothetical page that uses both without DOM-id collisions. 8 Vitest tests in OIDCTestConnectionPanel.test.tsx: * RunButton — disabled until issuer URL is non-empty * RunButton — also disabled when issuer URL is whitespace-only * RunButton — enabled when issuer URL is non-empty * HappyPath — all four primary checks render green with detail rows for authorization_url / token_url / userinfo_endpoint (asserts both the glyph contract AND the mocked POST body shape) * FailurePath — discovery=false renders ✗ on discovery + ✗ on JWKS + ⚠ on empty supported algs + error list with backend per-leg messages * IssParamFalse — load-bearing UX claim that the iss-parameter row renders · (informational), not ✗; body must contain the word 'informational' so operators understand it's not a failure * FetchError — top-level error rectangle when the POST throws * TestIDSuffix — same component mounted twice with different suffixes renders both without DOM-id collision Verify gate: * tsc --noEmit — clean * vitest OIDCTestConnectionPanel.test.tsx — 8/8 pass * vitest OIDCProvidersPage.test.tsx + OIDCProviderDetailPage.test.tsx — 38/38 pass (panel-mount in both pages does not regress existing tests because they don't trigger the test button) Operator runbook: the four glyph meanings are documented inline on the panel's subtitle. Audit doc annotation at cowork/auth-bundles-audit-2026-05-10.md flips MED-5 from 'BACKEND CLOSED' to 'CLOSED' with the GUI-half annotation. Refs cowork/auth-bundles-fixes-2026-05-11/09-med-oidc-test-connection-button.md. --- CHANGELOG.md | 23 ++ web/src/pages/auth/OIDCProviderDetailPage.tsx | 12 + web/src/pages/auth/OIDCProvidersPage.tsx | 11 + .../auth/OIDCTestConnectionPanel.test.tsx | 218 ++++++++++++++++++ .../pages/auth/OIDCTestConnectionPanel.tsx | 170 ++++++++++++++ 5 files changed, 434 insertions(+) create mode 100644 web/src/pages/auth/OIDCTestConnectionPanel.test.tsx create mode 100644 web/src/pages/auth/OIDCTestConnectionPanel.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index fbbbaea..528c7c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ ### Security +- **OIDC provider "Test connection" panel (Audit 2026-05-11 Fix 09 — MED-5 GUI half).** + MED-5's backend dry-run endpoint (`POST /api/v1/auth/oidc/test`, gated + `auth.oidc.create`) shipped on `dev/auth-bundle-2` but had no GUI caller — + the `authOIDCTestProvider` function in `web/src/api/client.ts` was dead + code. Operators had to complete the create form blind, save, then click + "Refresh" to discover whether the issuer URL worked; failures left a + broken provider row in the database that had to be deleted before + retrying. New shared component + `web/src/pages/auth/OIDCTestConnectionPanel.tsx` calls the backend + against the live form state and renders a four-row status panel inline: + Discovery fetched, JWKS reachable, supported algs (warns when the IdP + advertises none), and RFC 9207 iss-parameter advertisement (informational + `·` glyph, not ✗, because the spec is SHOULD). Backend per-leg `errors[]` + flow into an inline bullet list. The panel is mounted in the + OIDCProvidersPage create modal AND the OIDCProviderDetailPage edit form — + the edit-form half is load-bearing for verifying IdP rotations (Keycloak + realm rename, Okta tenant move) without committing first. Run button is + disabled until the issuer URL is non-empty (whitespace-trimmed); the + component is read-only — safe to run repeatedly. 8 Vitest tests pin the + glyph-vs-glyph contract (✓/✗/⚠/·), the button-disabled-without-issuer + shape, and the test-id-suffix collision-prevention when the panel is + mounted twice on the same page. + - **Scope-aware actor-role revoke (Audit 2026-05-11 A-4).** HIGH-10 made it possible to grant the same role to the same actor at multiple scopes (e.g. `r-operator` on `profile=p-acme` AND `profile=p-globex`) diff --git a/web/src/pages/auth/OIDCProviderDetailPage.tsx b/web/src/pages/auth/OIDCProviderDetailPage.tsx index 2bef966..da96d85 100644 --- a/web/src/pages/auth/OIDCProviderDetailPage.tsx +++ b/web/src/pages/auth/OIDCProviderDetailPage.tsx @@ -12,6 +12,7 @@ import { useAuthMe } from '../../hooks/useAuthMe'; import PageHeader from '../../components/PageHeader'; import ErrorState from '../../components/ErrorState'; import { validateEmailDomain } from './OIDCProvidersPage'; +import OIDCTestConnectionPanel from './OIDCTestConnectionPanel'; // ============================================================================= // Bundle 2 Phase 8 — OIDCProviderDetailPage. @@ -623,6 +624,17 @@ export default function OIDCProviderDetailPage() { )} {editing && ( <> + {/* Audit 2026-05-11 Fix 09 — Test Connection panel (MED-5 GUI half). + Lets the operator verify an issuer URL change post-rotation + (e.g. Keycloak realm rename, Okta tenant move) without + committing first. Reads from the live edit state so the + scope of the test matches what Save would persist. */} + + + {err && ( +
+ {err} +
+ )} + {result && ( + + )} + + ); +}