MultiSite.md: describe only current state and design rationale
Drop all before/after contrasts, deferred ideas, and possible follow-ups (Identity layer, per-realm remote-auth policy flag, same-device redirect variant, legacy-module deletion note).
This commit is contained in:
+23
-34
@@ -18,8 +18,8 @@ global users/orgs, N realms.
|
||||
**Global (single instance, shared across realms):**
|
||||
|
||||
| Data | Notes |
|
||||
| ------------------------------- | -------------------------------------------------------------- |
|
||||
| Organizations, Roles, Users | unchanged structs |
|
||||
| ------------------------------- | -------------------------------------------------------- |
|
||||
| Organizations, Roles, Users | one global collection |
|
||||
| Permissions | `domain` field host-scopes effectiveness |
|
||||
| Sessions | host-bound (`Session.host`, exact match) |
|
||||
| Credentials/passkeys | global collection, each stamped with its `rp_id` |
|
||||
@@ -36,13 +36,11 @@ global users/orgs, N realms.
|
||||
| `site_url`/`site_path` | runtime derivation, per realm |
|
||||
| OIDC provider (keys, clients) | per rp-id — each realm is an independent issuer |
|
||||
|
||||
The key simplification: `db.data()` stays a plain global singleton. A
|
||||
contextvar is needed only for the **current realm** (passkey, config,
|
||||
OIDC view) — not for database access.
|
||||
`db.data()` is a plain global singleton. A contextvar is needed only for
|
||||
the **current realm** (passkey, config, OIDC view) — not for database
|
||||
access.
|
||||
|
||||
A deferred idea remains an Identity layer above the org-owned User
|
||||
(`Identity → N org memberships + N credentials`). The architectural rule
|
||||
is already in place: **authentication establishes identity, not
|
||||
The architectural rule: **authentication establishes identity, not
|
||||
organization** — the requested hostname selects the org/permission
|
||||
context after authentication (via `Permission.domain` host-scoping and
|
||||
session host binding).
|
||||
@@ -73,7 +71,7 @@ the WS. On top of that:
|
||||
|
||||
- Origin rule: an origin is valid if it is in the rp-id subtree **or
|
||||
explicitly listed in the realm's configured origins**. Explicit listing
|
||||
is the trust boundary (`allowed_origins` was already an allow-list).
|
||||
is the trust boundary.
|
||||
- `GET /.well-known/webauthn` on the canonical rp-id host serves
|
||||
`{"origins": [...]}` from the realm's configured non-subdomain origins
|
||||
(404 when there are none).
|
||||
@@ -98,8 +96,7 @@ a cap (default 5) on related origins per realm.
|
||||
(2.B) and later consolidates to Related Origins re-enrolls: authenticate
|
||||
against the old realm (or via 2.C), register a new credential under the
|
||||
common rp-id, retire the old one. The per-credential rp-id badge (§9)
|
||||
makes this visible. No automated credential migration is provided or
|
||||
needed.
|
||||
makes this visible. There is no automated credential migration.
|
||||
|
||||
### 2.B Multiple rp-id realms under one administrative instance
|
||||
|
||||
@@ -311,8 +308,8 @@ effective_auth_host(realm) = realm.auth_host or first_configured_auth_host or No
|
||||
`RemoteAuthRequest.rp_id` — stamping them with the permitter's realm
|
||||
would break every cross-realm remote login. Registration-flow and OIDC
|
||||
codes stamp from the current realm (issue and redeem sides always
|
||||
match). The host re-check at set-session already binds `CookieCode`
|
||||
independently; the rp_id check is additive.
|
||||
match). The host re-check at set-session independently binds
|
||||
`CookieCode` to the host; the rp_id check complements it.
|
||||
|
||||
### 7.2 Remote authentication — cross-realm permits allowed
|
||||
|
||||
@@ -325,14 +322,9 @@ effective_auth_host(realm) = realm.auth_host or first_configured_auth_host or No
|
||||
request's rp-id is shown to the permitting user ("device at app2.com
|
||||
requests login").
|
||||
- No policy flag: cross-realm remote login is how the product works
|
||||
(users are global). A future per-realm policy field can add isolation.
|
||||
(users are global).
|
||||
- Exchange codes stay single-use, 60s, host-bound at redemption.
|
||||
|
||||
A possible follow-up is a same-device redirect variant ("logged in at the
|
||||
auth host, bounce to the app host with a code"): reuse `CookieCode` with
|
||||
`redirect_uri` + `state`; redeem at the target host's set-session as
|
||||
today.
|
||||
|
||||
## 8. OIDC: per-realm providers in one DB
|
||||
|
||||
- `DB.oidc` is `dict[str, OIDC]` keyed by rp-id. Each realm is an
|
||||
@@ -360,9 +352,8 @@ today.
|
||||
|
||||
- `GET /auth/api/settings`: per-request-Host realm values (rp_id,
|
||||
rp_name, effective `auth_host`, `own_auth_host`, site URLs).
|
||||
- **Realm management (master admin only, `auth:admin`)** — how new rp-ids
|
||||
are added after bootstrap, mirroring how rp-name was always edited post
|
||||
setup:
|
||||
- **Realm management (master admin only, `auth:admin`)** — how rp-ids are
|
||||
managed after bootstrap:
|
||||
- `GET/POST /auth/api/admin/realms/` and
|
||||
`PATCH/DELETE /auth/api/admin/realms/{rp_id}`. Writes require recent
|
||||
authentication (5 minutes).
|
||||
@@ -388,16 +379,15 @@ today.
|
||||
- Credential listings: `Credential.rp_id` serializes automatically into
|
||||
user-info and admin user detail responses; the frontend shows an rp-id
|
||||
badge **only when `credential.rp_id !== settings.rp_id`** — single-
|
||||
realm installs see no change.
|
||||
realm installs never show a badge.
|
||||
- **Enrollment prompt (2.C)**: when the user has no passkey for the
|
||||
current realm, the profile view offers to add one (a fresh remote-auth
|
||||
session satisfies the recent-auth requirement of registration).
|
||||
- Bootstrap/reset links use the default realm's URL.
|
||||
- **Bootstrap caveat handled**: the "admin has no credentials" startup
|
||||
check tests for an admin credential **under the default realm's
|
||||
rp-id** — with global users an admin may have passkeys only under
|
||||
another realm, and the printed link must still be usable for the
|
||||
default realm.
|
||||
- **Bootstrap check**: the "admin has no credentials" startup check tests
|
||||
for an admin credential **under the default realm's rp-id** — with
|
||||
global users an admin may have passkeys only under another realm, and
|
||||
the printed link must still be usable for the default realm.
|
||||
|
||||
## 10. Storage
|
||||
|
||||
@@ -418,15 +408,14 @@ today.
|
||||
as an explicit pre-flight step in the serve command, before the
|
||||
read-only startup open — read-only opens never trigger adoption or
|
||||
writes.
|
||||
- The legacy database's structs are kept in a separate module
|
||||
(`paskia/db/legacy.py`) that will be deleted once adoption is no longer
|
||||
supported. No multi-database merging exists or is needed.
|
||||
- The legacy database's structs live in a separate module
|
||||
(`paskia/db/legacy.py`). There is no multi-database merging.
|
||||
- The startup box prints per-realm lines.
|
||||
|
||||
## 11. Lifespan and background tasks
|
||||
|
||||
- One `Kanta` for `paskia.kantadb`, opened once in the lifespan; one
|
||||
background cleanup task — unchanged in shape (DB is global).
|
||||
background cleanup task (DB is global).
|
||||
- The kanta bootstrap hook only ever fires for a database created by
|
||||
`paskia init`; the serve command never bootstraps.
|
||||
- The registry is built from the stored `Config` after open; per-realm
|
||||
@@ -459,7 +448,7 @@ today.
|
||||
host dispatch, the well-known endpoint via the admin realm API, and a
|
||||
cross-realm remote login (request at test.localhost, permit at
|
||||
localhost, session valid on test.localhost) including the enrollment
|
||||
prompt UI. Related Origins get **no browser e2e**: a genuine related
|
||||
prompt UI. Related Origins have **no browser e2e**: a genuine related
|
||||
origin needs a non-subdomain host over HTTPS, and the browser fetches
|
||||
the well-known document itself — server-side coverage is in pytest
|
||||
(`tests/test_realms.py`).
|
||||
@@ -485,7 +474,7 @@ today.
|
||||
with registry-validated target host.
|
||||
- **Users/orgs global**: deleting a user/org cascades across all realms —
|
||||
intended. `auth:admin` is deployment-wide. Permission `domain`
|
||||
host-scoping unchanged.
|
||||
host-scopes effectiveness per host.
|
||||
- **Cross-realm permit transparency**: requesting realm/host shown to the
|
||||
approver; both sides logged.
|
||||
- **Secret hygiene in logs**: the OIDC signing-key censoring matches the
|
||||
|
||||
Reference in New Issue
Block a user