CLI: positional rp-id/rp-name; init adds domains to an existing database

- 'paskia init [rp-id] [rp-name]' and 'paskia migrate [rp-id]' are now
  positional; comma separation and the --rp-id/--rp-name flags are gone.
- With an existing paskia.kantadb, init adds the rp-id as a new domain
  (seeding its OIDC provider) or updates an existing domain's rp-name.
- Origin allow-list semantics clarified: the bare '*' entry allows
  anything within the rp-id domain on any scheme and port (also the
  empty-list default and its display in the admin UI, replacing the
  synthetic '*.rp-id' row); '*.x' wildcards are https-only; exact entries
  match scheme, host and port. Legacy '*.rp-id' wildcards migrate to '*'
  to preserve their any-scheme meaning.
This commit is contained in:
2026-09-07 03:07:38 +00:00
parent 476ce996ad
commit 7ff8869e1d
16 changed files with 293 additions and 129 deletions
+36 -27
View File
@@ -167,14 +167,18 @@ class Config(msgspec.Struct, omit_defaults=True):
URL, background jobs), the single configured domain is used, and with
several domains the first one sorted by rp-id — never for dispatch.
- Origin keys are bare hosts (`app.example.com`), wildcard patterns
(`*.example.com`), or full origins when not https
(`http://localhost:8080`)`https://` is omitted as the common case.
A dict value of `true` means presence only; an object carries extra
properties (currently just `auth_host`). Ordering carries no meaning —
display order is a UI affair.
(`*.example.com`), full origins when not https (`http://localhost:8080`),
or the bare `*``https://` is omitted as the common case. A dict value
of `true` means presence only; an object carries extra properties
(currently just `auth_host`). Ordering carries no meaning — display
order is a UI affair.
- An empty `origins` dict means the rp-id and all its subdomains may
sign in (the default). A non-empty dict is an allow-list of in-domain
sign-in sites; one entry may be marked `auth_host` (never a wildcard).
sign-in sites; matching semantics per entry kind:
- `*` — anything within the rp-id domain, any scheme and port;
- `*.example.com` — the base domain and its subdomains, **https only**;
- anything else — exact match on scheme, host and port.
One entry may be marked `auth_host` (never a wildcard or `*`).
- **Origin validation** — two separate concerns: `origins` entries must
be within the rp-id domain. `related` entries must be outside it, are
capped (default 5), must not be wildcards, and must not collide with
@@ -190,22 +194,28 @@ The CLI is split so that domain options exist only at bootstrap time —
they can never mix with runtime configuration of an already-configured
instance:
- **`paskia init`** — creates `paskia.kantadb` in CWD and seeds it:
- `--rp-id`: repeatable/comma-separated, default `["localhost"]`.
Multiple values create multiple domains at once (useful for
devserver/e2e).
- `--rp-name`: applies to the **first** rp-id only. Its purpose is that
the very first admin registration ceremony already shows the correct
RP name; everything else (origins, auth hosts, related domains) is
set up via the admin interface afterwards.
- **`paskia init [rp-id] [rp-name]`** — creates `paskia.kantadb` in CWD
and seeds it:
- `rp-id` (positional, default `localhost`) and `rp-name` (positional,
default same as rp-id) are the only bootstrap-time domain
configuration; the rp-name exists so the very first admin
registration ceremony already shows the correct name. Everything else
(origins, auth hosts, related domains) is set up via the admin
interface.
- `--listen`: stored into `Config.listen` (process-global).
- Seeds the admin user + registration reset link and prints the link.
Refuses to run if `paskia.kantadb` already exists, or if an
unconverted legacy `*.paskiadb` is present (`paskia migrate` converts
it first).
- **`paskia migrate`** — converts a legacy `<rp-id>.paskiadb` database
(§10) to `paskia.kantadb`. With several legacy candidates, `--rp-id`
selects `<rp-id>.paskiadb` by name; the others are left in place.
Refuses to run if an unconverted legacy `*.paskiadb` is present
(`paskia migrate` converts it first).
- **With an existing `paskia.kantadb`**, init instead adds the given
rp-id as a new domain (seeding its OIDC provider), or updates the
rp-name of an existing one — a convenience for what the admin
interface also does.
- **`paskia migrate [rp-id]`** — converts a legacy `<rp-id>.paskiadb`
database (§10) to `paskia.kantadb`. With several legacy candidates, the
positional rp-id selects `<rp-id>.paskiadb` by name; the others are
left in place. A legacy wildcard origin over the rp-id itself
(`*.example.com`) converts to the bare `*` entry, preserving its
any-scheme meaning.
- **`paskia`** — serve. Takes **no domain options**; only `--listen`
(per-run override of stored `Config.listen`, never persisted). Startup:
open `paskia.kantadb` → sanitize the stored domain set best-effort →
@@ -437,7 +447,7 @@ effective_auth_host(domain) = domain's own auth host or first configured one or
file) becomes `paskia.kantadb`, `users/` becomes `paskia.data/users/`,
and the old directory is renamed aside to `<name>.converted-bak`. A
lone candidate converts without options; with several candidates
`--rp-id <rp-id>` selects `<rp-id>.paskiadb` by name and the rest are
a positional rp-id selects `<rp-id>.paskiadb` by name and the rest are
left in place (e.g. a `*.bak.paskiadb` backup does not block
conversion). Empty directories are ignored. Conversion is an explicit
operator action, never a serve side effect — read-only opens never
@@ -465,18 +475,17 @@ effective_auth_host(domain) = domain's own auth host or first configured one or
## 12. Development
- `scripts/devserver.py`: bootstraps via one-shot `paskia init` when no
database exists (multi `--rp-id`, `--rp-name` for the first domain),
then runs plain `paskia` serve. Caddy dev origins iterate all bootstrap
rp-ids.
- `scripts/devserver.py`: bootstraps via one-shot `paskia init` per rp-id
when no database exists (rp-name for the first domain), then runs
plain `paskia` serve. Caddy dev origins iterate all bootstrap rp-ids.
- `PASKIA_AUTH_HOST` (consumed by `frontend/vite.config.js`) is a
comma-separated list of bare hostnames; the vite dev proxy forwards
`/.well-known/openid-configuration` and `/.well-known/webauthn` to the
backend.
- The example `caddy/auth/setup` snippet forwards both well-known paths
to paskia so a static `/.well-known/*` handler does not shadow them.
- E2E: `e2e/tests/global-setup.ts` runs `paskia init --rp-id
localhost,test.localhost` in the test-data dir (which doubles as the
- E2E: `e2e/tests/global-setup.ts` runs `paskia init localhost` and
`paskia init test.localhost` in the test-data dir (which doubles as the
server CWD) and serves; `e2e/tests/50-multidomain.spec.ts` exercises
host dispatch, the well-known endpoint via the admin domain API, and a
cross-domain remote login (request at test.localhost, permit at