Skip to content

feat(system-config): advertise the OpenID Provider on the public config - #573

Merged
Bccorb merged 4 commits into
mainfrom
feat/public-oidc-provider
Nov 6, 2026
Merged

Bccorb merged 4 commits into
mainfrom
feat/public-oidc-provider

Conversation

@Bccorb

@Bccorb Bccorb commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Advertises the OpenID Provider on the public config, so seamless profile login can find it (ADR 0003, fells-code/seamless-cli#316). Today nothing public offers the issuer or a client id, and the API's /.well-known/openid-configuration answers 404.

Refs #544

Draft until the @seamless-auth/types release from feat/public-oidc-provider is out. Then bump to it, update the lock, and run npm run generate:api. Until then CI fails at typecheck; local checks used a packed tarball.

What it does

  • New system config key oidc_cli_client_id, through the normal admin routes with their step-up and audit. An unknown or ineligible application is refused.
  • GET /system-config/public gains optional oidcProvider: { issuer, cliClientId }, present only when the provider is on, the issuer is set, and that application exists, is active, and is exchange enabled and eligible. Otherwise absent, never partial. Disabling the application makes it disappear at once, because the row is read on every request.
  • Both values are already public to anyone who can start a sign-in. The response is no-store.

Also in this PR

  • Bug fix: PATCH /system-config/admin answered 500 for any nullable key set to null, because system_config.value is NOT NULL. The route now removes the row, which reads back as null. This also fixes oidc_issuer, hosted_ui and max_concurrent_sessions.
  • New env var OIDC_CLI_CLIENT_ID, because a test requires every config key to be mapped. A value seeded from env skips the PATCH eligibility check, but the public route checks eligibility on every read. If an admin clears the key while the variable is set, it returns at the next start (documented).
  • ADR 0003 amended, confirmed by the maintainer 2026-11-05: the CLI learns the issuer and client from the public config; a loopback passkey step-up sends acr_values=phr with max_age=0, because max_age=300 usually records no step-up; and with no browser session the user runs the command a second time, a stated cost. Treating a fresh passkey sign-in as a step-up was not adopted, because it changes what a step-up means.

Known limits

  • Config keys go through the 5 minute in-process cache, so other replicas can lag a config change by up to 5 minutes.
  • On the adapters' JSON path, no-store is likely not forwarded (read from their source, not run). The CLI calls the API directly.
  • Docs site pages for this route are not updated yet.
  • The OpenAPI diff also picks up chore: update version and changelog #151's sessionExchangeEnabled default from the unreleased types, and the generated example prints "issuer": null, a generator placeholder.

Checks

f73d4a8 adds the tests alone and fails (11 of 28 on real Postgres). Lint, typecheck, format, check:changeset-bump and generate:api pass. Unit coverage 2924 passed, 99.07% lines; unit plus Postgres 3172 passed on two consecutive runs. One unrelated test (identityFlowLimits.spec.ts) failed once and passed on every rerun. f73d4a8 used --no-verify, because the hook runs coverage.

Covers each case where oidcProvider must be absent, refusing an
ineligible oidc_cli_client_id, the advertisement disappearing when the
application is disabled, deleted or has its exchange turned off, and
clearing the key with null. Fails until the route and key exist.
Adds the oidc_cli_client_id key, refused unless it names an active
application eligible for session exchange, and an optional oidcProvider
on GET /system-config/public, sent whole or not at all (ADR 0003). The
application is read on every request, so disabling it ends the
advertisement at once.

PATCH /system-config/admin answered 500 for any nullable key set to
null, because system_config.value is NOT NULL. It now removes the row,
which reads back as the schema's null.
The CLI learns the issuer and client from /system-config/public, and a
loopback passkey step-up sends max_age=0, both confirmed by Brandon on
2026-11-05. Updates the API contract and configuration docs.
@Bccorb
Bccorb marked this pull request as ready for review November 6, 2026 03:41
@Bccorb
Bccorb merged commit 2f1c3db into main Nov 6, 2026
9 checks passed
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.

1 participant