T-HUB/docs/design/2026-10-13-files-oidc-sso.md
glass beadian b8671113f5 docs: publish governance, governed boosts, receipts, OIDC phase 2, files SSO
Engineering guide: session/refresh/logout architecture, publish-governance
and amplify gating sections, file map to migrations 001-034. Admin guide:
governance administration, files-SSO operations, verification harnesses,
migration list + deploy order + 034 session note. User manual: calendar
receipt badges, boost gating, connections governance note (replaces the
stale 'stub' statuses). Design doc 2026-10-13: marked implemented with the
as-shipped phase 2. Wiki (EN/ES): Drive sign-in + boosts/approvals.
README: governance + Files SSO bullets.
2026-10-14 11:52:48 +00:00

5.2 KiB
Raw Permalink Blame History

Files SSO design — Hub as the OIDC issuer for oCIS

Status: implemented (phase 1: provider + sign-in, shipped; phase 2: session-bound refresh tokens + logout propagation, shipped — see §Phases update below) Date: 2026-10-13 Related: docs/design/2026-10-12-collectives-architecture.md, brand review P0 #2 (files.occupymedia.cc navy login wall), matrix-sso module (chat SSO, shipped).

Problem

The Hub's Drive view embeds oCIS web (files.occupymedia.cc). Today:

  • No Hub user has an oCIS account. The API reaches oCIS through one shared service account (FILES_OCID_USER); the collective Library lives in that account's space.
  • The embedded oCIS web shows its own sign-in screen for a login the user does not have, in ownCloud's default navy/white theme — the single worst brand break measured in the review.

Decision

The Hub becomes the OIDC identity provider; oCIS trusts it as its external issuer. This mirrors what just shipped for chat: the Hub is already the identity provider for Matrix (JWT broker). Files becomes the second relying party of the same identity, not a third account system.

Rejected alternative: a standalone IdP (Authelia/Authentik/Keycloak). It adds a service to run and a second admin surface for one relying party; the Hub already holds the session, the RBAC roles, and the username namespace.

Flow (target state)

  1. User opens /files/drive in the Hub. Their Hub session cookie proves who they are.
  2. The embedded oCIS web redirects to https://api.occupymedia.cc/oidc/authorize (client id web, PKCE required).
  3. The Hub is already signed in → no login prompt, no consent screen for this first-party client (auto-approve). A short-lived code returns to oCIS.
  4. oCIS exchanges the code at /oidc/token, reads claims (preferred_username = Hub username, email, name), and provisions the oCIS account on first login (oCIS does this natively when OCIS_OIDC_ISSUER points at an external issuer).
  5. The user lands in their own drive. The navy wall is gone because there is no wall.

What the Hub must add

  • apps/api/src/oidc/: an OIDC provider module — /.well-known/openid-configuration, /oidc/authorize, /oidc/token, /oidc/userinfo, /oidc/jwks. Signing keys in the existing vault pattern (ES256 or RS256, rotated like DEKs). Session binding to the existing Hub session cookie; codes single-use, ≤60 s TTL, PKCE S256 enforced.
  • Auto-consent policy: client id web (the oCIS web app) is first-party and skips the consent screen. Any other client id requires explicit consent — fail closed.
  • Claim mapping: preferred_username must equal the Hub username verbatim (same invariant the Matrix SSO module already enforces: Hub usernames are a subset of the localpart grammar, case-folded identity is refused).

What oCIS must change

  • OCIS_OIDC_ISSUER=https://api.occupymedia.cc/oidc
  • WEB_OIDC_CLIENT_ID=web, scope openid profile email
  • oCIS auto-provisions accounts from claims (default behaviour with an external issuer). The FILES_OCID_USER service account stays for the Library's WebDAV path.

Phases

  1. Provider + sign-in (≈1–2 days): the module above; Drive view switches authMode to "oidc"; the navy wall disappears. Shipped.
  2. Personal drives (≈1 day): uploaded files land in the user's own drive; the Library keeps serving collective folders from the service account. Shipped (autoprovisioned personal spaces verified live).
  3. Share-through (≈1 day, optional): group folders shared into members' drives so the Drive tab also shows collective Library content; only then consider deprecating the Library tab. (open)

Phase 2 as shipped (refresh + logout, migration 034)

The security note below said logout propagation was a nice-to-have; it shipped as part of hardening instead:

  • auth_sessions rows (sid in the session payload): login/reissue write the row, POST /auth/logout revokes it, reissue refuses revoked or pre-034 tokens. Request validation stays stateless.
  • oidc_refresh_tokens: opaque tokens, SHA-256 at rest, rotate-on-use, family_id chains with reuse detection (presenting a consumed token revokes the chain). Every refresh re-checks the session row and active membership — so an oCIS session can never outlive the Hub sign-in that minted it, and logout propagates within one access-token TTL (≤1 h).
  • The token endpoint is unauthenticated: lookups go through the definer-rights oidc_refresh_lookup() (audit_append pattern). Refresh TTL has no constant — it is the Hub session's remaining lifetime.
  • Matrix SSO stays out of propagation scope by design: the broker mints 2-minute m.login.tokens and Element holds a Matrix-native session after.

Security notes

  • The OIDC provider must never accept the service-account identity for a browser session; tokens are minted only from a live Hub session.
  • Logout: Hub logout revokes the session cookie; oCIS tokens expire on their own TTL (oCIS refresh behaviour is issuer-driven). Single-logout signaling to oCIS is a nice-to-have, not a blocker.
  • The issuer URL must be exactly https://api.occupymedia.cc/oidc everywhere (issuer discovery, oCIS config, token audience) — mismatch breaks token validation.