Skip to content

Phase 2b: OIDC client registration at app install + {{ oidc.* }} template vars - #149

Open
ClaydeCode wants to merge 5 commits into
feature/oidc-phase2a-providerfrom
feature/oidc-phase2b-install
Open

Phase 2b: OIDC client registration at app install + {{ oidc.* }} template vars#149
ClaydeCode wants to merge 5 commits into
feature/oidc-phase2a-providerfrom
feature/oidc-phase2b-install

Conversation

@ClaydeCode

Copy link
Copy Markdown
Contributor

Phase 2b of the multi-user identity rollout: apps get OIDC credentials automatically at install. Stacked on the phase-2a provider PR.

What this does

  • app_meta.json gains an optional oidc section (redirect_uris — may reference {{ portal.domain }} —, public_client, scope).
  • Client registration happens inside compose-template rendering: render_docker_compose_template registers/refreshes the app's OIDC client and passes {{ oidc.client_id }}, {{ oidc.client_secret }}, {{ oidc.issuer }} to the template. Because rendering runs at install, reinstall AND every startup, redirect URIs heal automatically (e.g. after a domain change) — no extra hook needed.
  • Credentials are stable: the upsert preserves client_id/client_secret on conflict, so reinstalls and re-renders never invalidate an app's configured client.
  • Uninstall removes the client row, cascading the app's codes and tokens.

Apps without an oidc section are untouched (oidc=None in the template context).

Companion PR

app-repository: paperless-ngx catalog entry using this mechanism (allauth openid_connect via env, auto-signup, SSO redirect) — the admin-vs-daily-account request from freeshard#141. Ships app-by-app; more apps follow independently. Immich needs its config-file/API mechanism and is deliberately not in this pass.

Tests

Three integration tests over the real install worker + Docker using a new oidc_app in the mock store: install registers the client with domain-rendered redirect URIs and renders creds into the compose file; reinstall keeps credentials; uninstall removes the client.

Recommended reading order

  1. shard_core/data_model/app_meta.pyOidcMeta
  2. shard_core/database/oidc.py — upsert preserves creds; get/remove by app_name
  3. shard_core/service/oidc_provider.pyensure_app_client(), issuer_for_domain()
  4. shard_core/service/app_installation/util.py — render hook
  5. shard_core/service/app_installation/worker.py — uninstall cleanup
  6. tests/mock_app_store/oidc_app/*, tests/test_oidc_app_install.py

🤖 Generated with Claude Code

Test status

New install tests 3/3 green; regression run over test_app_installation.py + test_oidc.py + test_users.py: 29 passed, 0 failed.

ClaydeCode and others added 2 commits July 27, 2026 19:52
…} template vars

app_meta.json gains an optional oidc section (redirect_uris — may reference
{{ portal.domain }} —, public_client, scope). Compose-template rendering
registers/refreshes the app's OIDC client and passes oidc.client_id,
oidc.client_secret and oidc.issuer into the template. Registration lives in
the render step because rendering runs at install, reinstall and every
startup — redirect URIs heal automatically after a domain change with no
extra hook.

The client upsert preserves client_id/client_secret on conflict so
reinstalls and re-renders never invalidate an app's configured credentials.
Uninstalling an app removes its client row, cascading its codes and tokens.

Apps without an oidc section are unaffected (oidc=None in the context).

Phase 2b of the multi-user identity rollout; companion app-repository PR
adds the first catalog app (paperless-ngx) on this mechanism.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A shard that is not in the OIDC rollout answers 404 on every provider endpoint,
so registering a client for an installed app would write credentials pointing at
an IdP that does not respond. Gate the registration on the same flag and log a
warning, since the app's template then renders with empty OIDC_* values — a
state worth finding in the logs rather than in the app's own error output.

The blank-credential path is what makes this the flag's boundary: the flag
gates shard_core, not the app store, so an app whose app_meta.json declares an
oidc section stays installable on a shard where the provider is off. Do not
publish such an app to shards outside the rollout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Apps that implement OIDC Back-Channel Logout 1.0 can now advertise the
endpoint in their app_meta oidc block, so the shard can sign that device
out of the app when its terminal is un-paired. Rendered and stored at
client registration alongside redirect_uris.

Optional by design: an app that omits it cannot be signed out from our
side, only from its own UI. Making that visible in metadata is what lets
us say which apps are fully revocable.

Delivery itself is not built here — see the follow-up issue.

Refs #189
Renders backchannel_logout_uri from app_meta the same way redirect_uris
are rendered and stores it on the client row, so the shard knows where to
send a logout token when a device is un-paired.

The mock app's zip is rebuilt because the app store serves the committed
zip, not the loose app_meta.json beside it -- editing only the loose file
is a silent no-op.

Delivery is not implemented here; see #192.

Refs #189
@ClaydeCode
ClaydeCode force-pushed the feature/oidc-phase2b-install branch from 67b8081 to fd4266f Compare July 31, 2026 11:44
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