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.
106 lines
5.2 KiB
Markdown
106 lines
5.2 KiB
Markdown
# 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.token`s 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.
|