Skip to content

feat: OIDC JWKS staleness alarm and forced refresh endpoint - #809

Open
Hollujay wants to merge 1 commit into
RevoraOrg:masterfrom
Hollujay:feat/oidc-jwks-staleness-alarm
Open

feat: OIDC JWKS staleness alarm and forced refresh endpoint#809
Hollujay wants to merge 1 commit into
RevoraOrg:masterfrom
Hollujay:feat/oidc-jwks-staleness-alarm

Conversation

@Hollujay

@Hollujay Hollujay commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements #686 — OIDC JWKS staleness alarm and forced refresh endpoint.

Per-issuer JWKS cache age tracking, a Prometheus staleness gauge, and an admin-only dual-control force-refresh endpoint with rate limiting, audit events, and request coalescing. Replaces the previous single-admin confirmation approach (header + body boolean) merged in #589 with a genuine two-admin approval flow, per the issue requirements.

New endpoint POST /auth/oidc/jwks/refresh (+ alias POST /api/auth/oidc/jwks/refresh):

  1. Propose (admin A): { issuerUrl?: string }202 + approvalId. Omitted issuer = refresh all tracked issuers.
  2. Approve (distinct admin B, within 5-min TTL): { approvalId }200 + { refreshedIssuers, failedIssuers } (502 when all fail, partial-failure reporting otherwise).

Changes

  • src/auth/oidc/jwksCache.ts — per-issuer fetchedAt/last-refresh tracking, getCacheAgeSeconds(issuer), getTrackedIssuers(), gauge emission (oidc.jwks.age_seconds labeled by issuer, 60s unref'd ticker), single-flight coalescing per JWKS URI with a pendingIssuers registry so every issuer awaiting a shared fetch is accounted for; in-flight slot released on success and failure.
  • src/auth/oidc/jwksRefreshApprovalGate.ts — new in-memory dual-control gate: proposer ≠ approver, TTL expiry, one-approval-per-proposal, distinct error types.
  • src/auth/oidc/oidcAdapterService.tsrefreshAllJwks() with per-issuer Promise.allSettled partial-failure results, getTrackedJwksIssuers().
  • src/auth/oidc/oidcRoute.ts — dual-control handler, shared createRateLimitMiddleware (10 req/min per admin, keyPrefix: 'oidc-jwks-refresh'), enriched audit events (actor/proposer/approver/scope/status/reason), non-admins rejected before the gate/limiter.
  • docs/oidc-jwks-staleness-alarm.md — design, threat model, security assumptions, runbook.

Security notes

  • Dual control: refresh requires two distinct admins; self-approval rejected (409); proposals expire after 5 minutes. Modeled on the repo's existing dual-control patterns (OFAC review queue, tenant-settings propose/approve, legal-hold).
  • Rate limiting: reuses the shared per-user middleware keyed on the authenticated admin; bucket isolation verified in tests with an in-memory store.
  • No failed-fetch caching: a failed fetch releases the coalescing slot; the next request retries.
  • Failure handling: all-issuer refresh reports per-issuer failures; complete failure returns 502 with per-issuer reasons. Audit events record every outcome.
  • Review flag (new pattern): the dual-control gate is in-memory (single-instance semantics). Flagged in the docs — a distributed store (e.g., Redis) is a follow-up if multi-instance approval is required.

Test results

Full OIDC module (npx jest --runInBand src/auth/oidc/): 153 passed, 0 failed (7 suites).

Module coverage (--coverage on src/auth/oidc/*.ts):

Metric Value
Statements 98.63%
Branches 90.37% (new code ≥ 97%; gaps are pre-existing handler paths)
Functions 97.01%
Lines 100%

npx tsc --noEmit: 0 errors in changed files. Baseline upstream master has 438 pre-existing errors; this branch 430 (the diff fixes 6 of its own plus 2 pre-existing in oidc.test.ts).

Pre-existing repo conditions (not introduced here)

  • Full suite is red on upstream master in this environment: verified baseline run on clean 48e4dcd0 — 215/224 suites fail to even load due to a ts-jest getCacheKey bug under Node 24 (environment incompatibility), plus pre-existing failures (missing STELLAR_SERVER_SECRET, DB-backed suites, tests written against older signatures). Full-suite run on this branch: 1957 passed / 284 failed, with zero real failures in OIDC/health suites.
  • .github/workflows/ci.yml does not execute jest (only npm ci, audit, alert-mapping validation).
  • The fork's master carried a duplicate amlAuditRepo declaration in src/index.ts and a missing consumedJtis field in oidcAdapterService.ts; both are already fixed upstream and confirmed identical after rebase.
  • Branch is rebased onto current upstream master (48e4dcd0) — clean 10-file diff, linear history.

Closes #686

@drips-wave

drips-wave Bot commented Aug 3, 2026

Copy link
Copy Markdown

@Hollujay Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Hollujay
Hollujay force-pushed the feat/oidc-jwks-staleness-alarm branch from fcf4feb to 967bdff Compare August 3, 2026 04:43
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.

OIDC JWKS cache staleness alarm and forced refresh endpoint

1 participant