mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 14:51:30 +00:00
e92af14a22
Audit 2026-05-11 Fix 10 closure. MED-7's backend endpoint
GET /api/v1/auth/oidc/providers/{id}/jwks-status (commit 172b30b)
shipped the per-provider verifier counters on dev/auth-bundle-2
but the GUI never called it — authOIDCJWKSStatus in the API
client was dead code. The audit doc had prematurely flipped the
MED-7 row to CLOSED; this closure makes the claim true.
Operator gap before this fix: operators investigating 'why is
login failing for this IdP?' could not see last_refresh_at,
rejected_jws_count, or last_error from the GUI. They had to drop
to curl.
New shared component web/src/pages/auth/OIDCJWKSStatusPanel.tsx
queries the endpoint via TanStack Query and renders six dt/dd
rows with operator-readable sentinels for each empty case:
* Last refresh — RFC 3339 timestamp; '(never — cold cache)'
sentinel when the IdP has never been hit.
* Refresh count — cumulative since process boot.
* Rejected JWS count — number of ID tokens that failed signature
verification. Step-changes correlate to IdP key rotations.
* Last error — most recent JWKS-refresh failure (sanitized — no
token content). Red treatment when non-empty; '(none)' sentinel
for healthy state.
* RFC 9207 iss param — 'supported by IdP' / 'not advertised'.
Informational only; the operator-side verifier still demands
the param by default.
* Current KIDs — cache contents; '(not exposed — query jwks_uri
directly)' sentinel when the backend declines to expose the
list (the backend may withhold them for opacity).
Refresh-now button:
* Calls POST /api/v1/auth/oidc/providers/{id}/refresh
(RefreshKeys path), then invalidates the panel's query so the
freshly-updated counters render without a page reload.
* Refresh failures surface as an inline red rectangle and do NOT
hide the existing snapshot — partial visibility is better than
no visibility.
* Hidden when the optional canRefresh prop is false. The
OIDCProviderDetailPage mount wires canRefresh to
useAuthMe().hasPerm('auth.oidc.edit') so viewer-class callers
see the read-only panel.
Permission gating:
* The backend endpoint is gated auth.oidc.list. Callers without
the permission get HTTP 403; the panel's TanStack query is
configured with retry: 0 so a 403 doesn't drown the page in
retries, and the panel returns null when the query errors —
hiding silently for callers who can't see the data.
* The Refresh-now button is hidden for callers without
auth.oidc.edit. Read-only callers still see the panel +
counters.
Mount: OIDCProviderDetailPage.tsx between the read-only field
display section and the Actions section. canRefresh wired to
the canEdit boolean already computed at the page level.
9 Vitest tests in OIDCJWKSStatusPanel.test.tsx:
* LoadingState — query in flight, Loading… visible.
* HappyPath — all six dt/dd pairs visible with operator-readable
values; current KIDs joined comma-separated.
* 403 — authOIDCJWKSStatus errors, panel returns null, no DOM
artifacts left behind.
* RefreshNow — calls refreshOIDCProvider('op-okta'), invalidates
the status query, the panel re-fetches and re-renders with the
new refresh_count (mock returns different snapshots on the
two calls).
* RefreshNow surfaces refresh-failure inline without hiding the
panel (preserves the existing snapshot so the operator can
read pre-failure state).
* NeverRefreshed — last_refresh_at='' renders the cold-cache
sentinel rather than a blank cell.
* CurrentKIDsEmpty — empty list renders the 'not exposed'
sentinel rather than a blank cell.
* LastError — non-empty last_error renders with red treatment.
* CanRefreshFalse — panel + counters render; Refresh-now button
is gone.
Verify gate:
* tsc --noEmit — clean
* vitest OIDCJWKSStatusPanel.test.tsx — 9/9 pass
* vitest OIDCProviderDetailPage.test.tsx — 19/19 pass (panel
mount does not break existing tests because the unmocked
authOIDCJWKSStatus call in those tests rejects, the panel
returns null, and the rest of the page renders normally)
Audit doc annotation at cowork/auth-bundles-audit-2026-05-10.md
flips MED-7 from the premature CLOSED claim to a properly-staged
'Backend CLOSED 2026-05-10 + GUI half CLOSED 2026-05-11'
annotation describing the panel + tests.
Refs cowork/auth-bundles-fixes-2026-05-11/10-med-jwks-status-panel.md.