docs: files SSO design pass — Hub as the OIDC issuer for oCIS

Phase 1 kills the navy login wall (provider + PKCE + auto-consent for the
first-party oCIS web client); phases 2-3 move uploads into personal drives
and share collective folders into them. Mirrors the Matrix JWT broker: the
Hub is the identity, oCIS is a relying party.
This commit is contained in:
glass beadian 2026-10-13 16:52:33 +00:00
parent dae302bd18
commit 5bc81c16f2

View file

@ -0,0 +1,86 @@
# Files SSO design — Hub as the OIDC issuer for oCIS
Status: proposed (design pass; not implemented)
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.
2. **Personal drives** (≈1 day): uploaded files land in the user's own drive;
the Library keeps serving collective folders from the service account.
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.
## 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.