feat(api): surface per-OIDC-instance status on /api/health #323

Merged
james merged 1 commit from 214-oidc-health into main 2026-06-28 18:46:27 +00:00
Owner

Resolves the ADR-0017 deferred follow-up (#214): a misconfigured OIDC instance no longer fails silently (button missing, warning only in container logs).

  • GET /api/health now returns a providers array — one entry per configured OIDC_<NAME>_ISSUER — as { id, status, lastError? } with status in ok | discovery_failed | override_invalid | disabled.
  • OIDCEndpointError gains a kind discriminator + a secret-free publicDetail; a module-level snapshot keeps the unauthenticated probe cheap (no per-request discovery — failed discovery isn't otherwise memoized, so the snapshot is what stops a broken instance hitting the network on every probe).
  • providers is informational only: it never changes the top-level status or the 200/503 (still DB-driven), so a bad sign-in button can't trip a container health probe. [] when no OIDC is configured.
  • lastError is generic/secret-free (env-var names or fixed phrases) — never a client secret, raw env value, or IdP response body. Tests assert the secret and the doc's claimed issuer never appear in the response.
  • OpenAPI: /api/health registered with HealthDto and removed from the coverage exclusion; openapi.json + api-client types regenerated.
  • Docs: new Diagnostics section in docs/oidc-self-hoster-guide.md.

Status semantics

status produced when
ok resolveOIDCEndpoints succeeded
discovery_failed discovery doc unreachable/malformed/issuer-mismatch/missing endpoint
override_invalid an endpoint override isn't https://
disabled parse-stage drop-out (bad name, built-in collision, empty issuer, missing client id/secret)

Verification

  • pnpm -F @carol/api test817 pass / 175 skipped (new tests/auth/oidc-health.test.ts + tests/api/health.test.ts; Postgres legs skip locally — change touches no DB schema/queries, so it's engine-agnostic).
  • openapi:check up-to-date, openapi:coverage 104 pairs.
  • @carol/api-client + @carol/client typecheck — clean.

Closes #214

🤖 Generated with Claude Code

Resolves the ADR-0017 deferred follow-up (#214): a misconfigured OIDC instance no longer fails silently (button missing, warning only in container logs). - `GET /api/health` now returns a `providers` array — one entry per configured `OIDC_<NAME>_ISSUER` — as `{ id, status, lastError? }` with `status` in `ok | discovery_failed | override_invalid | disabled`. - `OIDCEndpointError` gains a `kind` discriminator + a secret-free `publicDetail`; a module-level snapshot keeps the unauthenticated probe cheap (no per-request discovery — failed discovery isn't otherwise memoized, so the snapshot is what stops a broken instance hitting the network on every probe). - `providers` is **informational only**: it never changes the top-level `status` or the 200/503 (still DB-driven), so a bad sign-in button can't trip a container health probe. `[]` when no OIDC is configured. - `lastError` is generic/secret-free (env-var names or fixed phrases) — never a client secret, raw env value, or IdP response body. Tests assert the secret and the doc's claimed issuer never appear in the response. - OpenAPI: `/api/health` registered with `HealthDto` and removed from the coverage exclusion; `openapi.json` + api-client types regenerated. - Docs: new **Diagnostics** section in `docs/oidc-self-hoster-guide.md`. ## Status semantics | status | produced when | |---|---| | `ok` | `resolveOIDCEndpoints` succeeded | | `discovery_failed` | discovery doc unreachable/malformed/issuer-mismatch/missing endpoint | | `override_invalid` | an endpoint override isn't `https://` | | `disabled` | parse-stage drop-out (bad name, built-in collision, empty issuer, missing client id/secret) | ## Verification - `pnpm -F @carol/api test` — **817 pass / 175 skipped** (new `tests/auth/oidc-health.test.ts` + `tests/api/health.test.ts`; Postgres legs skip locally — change touches no DB schema/queries, so it's engine-agnostic). - `openapi:check` up-to-date, `openapi:coverage` 104 pairs. - `@carol/api-client` + `@carol/client` typecheck — clean. Closes #214 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(api): surface per-OIDC-instance status on /api/health
All checks were successful
Commits / Conventional Commits (pull_request) Successful in 4s
PR / OSV-Scanner (pull_request) Successful in 1m38s
PR / Static analysis (pull_request) Successful in 2m10s
PR / Lint (pull_request) Successful in 2m35s
PR / pnpm audit (pull_request) Successful in 3m8s
PR / OpenAPI (pull_request) Successful in 3m14s
PR / Typecheck (pull_request) Successful in 3m14s
PR / Client (web export smoke) (pull_request) Successful in 3m23s
PR / Build (pull_request) Successful in 3m37s
PR / Test (postgres) (pull_request) Successful in 3m42s
PR / Package age policy (soft) (pull_request) Successful in 1m6s
PR / Test (sqlite) (pull_request) Successful in 3m48s
Secrets / gitleaks (pull_request) Successful in 41s
PR / Trivy (image) (pull_request) Successful in 2m11s
PR / Coverage (soft) (pull_request) Successful in 2m5s
0710cb087f
Resolves the ADR-0017 deferred follow-up (#214): a misconfigured OIDC
instance no longer fails silently (button missing, warning only in
container logs).

GET /api/health now returns a `providers` array — one entry per
configured OIDC_<NAME>_ISSUER — as { id, status, lastError? } with status
in ok | discovery_failed | override_invalid | disabled. OIDCEndpointError
gains a `kind` discriminator + a secret-free `publicDetail`; a
module-level snapshot keeps the unauthenticated probe cheap (no
per-request discovery; failed discovery isn't otherwise memoized).

providers is informational only: it never changes the top-level status or
the 200/503 (still DB-driven), so a bad sign-in button can't trip a
container health probe. lastError is generic/secret-free (env-var names
or fixed phrases) — never a client secret, raw env value, or IdP response
body.

OpenAPI: /api/health registered with HealthDto and removed from the
coverage exclusion; openapi.json + api-client types regenerated. Docs:
new Diagnostics section in docs/oidc-self-hoster-guide.md.

Closes #214

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

📊 Test coverage

Patch coverage: no testable lines changed.

Overall (app/, lib/, db/, excluding UI per ADR-0019):

Metric Value Soft target
Lines 81.9% ≥ 50%
Branches 73.3% ⚠️ ≥ 75%
Functions 91.5% informational

Soft thresholds per ADR-0019. Coverage is informational and does not block merge.

<!-- coverage-comment --> ## 📊 Test coverage **Patch coverage:** no testable lines changed. **Overall** (`app/`, `lib/`, `db/`, excluding UI per ADR-0019): | Metric | Value | Soft target | |---|---|---| | Lines | 81.9% ✅ | ≥ 50% | | Branches | 73.3% ⚠️ | ≥ 75% | | Functions | 91.5% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
james merged commit d51af86e5e into main 2026-06-28 18:46:27 +00:00
james deleted branch 214-oidc-health 2026-06-28 18:46:27 +00:00
Sign in to join this conversation.
No description provided.