From bb5a11fe6fc025111c9dd95ba5b513e844490c7b Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Fri, 19 Dec 2025 17:28:54 +0000 Subject: [PATCH] Docs updates. --- API.md | 237 +++++++++++------------------------------------------ Caddy.md | 58 ++++++++----- Headers.md | 13 +-- README.md | 4 +- 4 files changed, 92 insertions(+), 220 deletions(-) diff --git a/API.md b/API.md index a1f5c73..a5acb24 100644 --- a/API.md +++ b/API.md @@ -1,203 +1,58 @@ -# Paskia HTTP + WebSocket surface +# Paskia endpoints (integration-focused) -This document is a factual map of the URLs Paskia exposes. +Use these tables when integrating Paskia authentication into your app. -Two important distinctions: +## Behavior when `--auth-host` is enabled -1. **User‑navigable endpoints**: URLs a human types/scans into a browser (address bar / QR). -2. **API endpoints**: URLs the app uses programmatically (frontend or backend). +| Path / type | What happens on non-auth hosts | +|---|---| +| `/auth/api/*` | Served normally | +| `/auth/api/user/*` | `404` | +| `/auth/api/admin/*` | `404` | +| `/auth/ws/*` | `404` | +| Browser UI URLs like `/admin/`, `/{token}`, and legacy `/auth/...` | `307` redirect to the auth host (UI redirects strip the `/auth` prefix) | -Within **API endpoints**, the most important group comes first: +## Browser URLs (user-navigable) -- **`/auth/api/*`**: stable API surface that is reachable on any host (including non‑auth hosts). -- **Auth‑host‑only namespaces**: only reachable on the dedicated auth host when `--auth-host` is configured. +| Method | Path | What it is for | Notes | +|---:|---|---|---| +| GET | `/` | User profile UI | On app hosts you can also use `/auth/` | +| GET | `/auth/` | User profile UI (legacy entry) | Redirects to `/` on the auth host | +| GET | `/admin/` | Admin UI | Legacy entry: `/auth/admin/` | +| GET | `/{token}` | Reset / registration / device-add UI | Legacy entry: `/auth/{token}`; invalid tokens return `404` | +| GET | `/auth/restricted/` | HTML UI used by the auth-forward flow | Typically shown via `/auth/api/forward` on failures | -## Host modes (what changes when `--auth-host` is set) +## App HTTP API (reachable on any host): `/auth/api/*` -Paskia can run in either mode: +| Method | Path | Used for | Inputs / outputs (high level) | +|---:|---|---|---| +| POST | `/auth/api/validate` | Validate session cookie; optionally renew | Query: `perm` (repeatable). JSON response includes `valid`, `user_uuid`, `renewed` | +| GET | `/auth/api/forward` | Reverse-proxy auth check (Caddy/Nginx) | Query: `perm` (repeatable), `max_age`. Success: `204` with `Remote-*` headers; failures: `4xx` JSON or HTML depending on `Accept` | +| POST | `/auth/api/set-session` | Turn a Bearer session token into a cookie | Header: `Authorization: Bearer `; sets cookie | +| POST | `/auth/api/logout` | Clear cookie and delete session (best-effort) | Returns JSON message | +| GET | `/auth/api/settings` | Fetch runtime settings for clients | Returns RP info + base paths + auth-host info | +| GET | `/auth/api/token-info` | Validate reset token and fetch minimal metadata | Query: `token=`; invalid/expired returns `404` | +| POST | `/auth/api/user-info` | Fetch user info for UI | Either session cookie, or `reset=` for reset flows | -- **Multi‑host** (default, no `--auth-host`): all routes below are served on the current host. -- **Dedicated auth host** (`--auth-host auth.example.com`): - - Certain UI routes are redirected to the auth host. - - Certain sensitive API and WebSocket namespaces are **not served** on non‑auth hosts (they return `404`). +See [Headers.md](Headers.md) for the `Remote-*` header contract used by `/auth/api/forward`. -Rules are enforced by middleware in `paskia/fastapi/auth_host.py`. +## Auth-host-only HTTP APIs (usually not needed for app integration) -### Summary table (dedicated auth host mode) +| Audience | Method | Path | Used for | +|---:|---:|---|---| +| User | PUT | `/auth/api/user/display-name` | Update the user’s display name | +| User | POST | `/auth/api/user/logout-all` | Terminate all user sessions and clear cookie | +| User | DELETE | `/auth/api/user/session/{session_id}` | Terminate one session | +| User | DELETE | `/auth/api/user/credential/{uuid}` | Delete a credential (requires recent auth) | +| User | POST | `/auth/api/user/create-link` | Create a device-add link (requires recent auth) | +| Admin | GET | `/auth/api/admin/` | Admin UI entry (serves the admin SPA HTML) | +| Admin | * | `/auth/api/admin/*` | Admin management API (orgs, roles, users, permissions) | -| Category | Example path | On non‑auth hosts | On auth host | -|---|---|---:|---:| -| User‑navigable UI (root/admin/token) | `/admin/`, `/{token}` | 307 redirect (strip `/auth` when present) | served locally | -| Static assets | `/auth/assets/*` | served locally | served locally | -| App API (stable surface) | `/auth/api/validate` | served locally | served locally | -| Auth‑host‑only API | `/auth/api/admin/*`, `/auth/api/user/*` | **404** | served locally | -| Auth‑host‑only WebSockets | `/auth/ws/*` | **404** | served locally | +## WebSockets (auth-host-only when `--auth-host` is enabled) -Notes: -- “Strip `/auth`” applies only to UI redirects (e.g. `/auth/admin/` → `/admin/` on the auth host). -- Redirects use HTTP `307` so method/body are preserved. - ---- - -## API endpoints (programmatic) - -### 1) Stable app API (reachable on any host): `/auth/api/*` - -These endpoints are intended for programmatic use by: - -- the browser frontend, and/or -- backend services (reverse proxies, gateways) that need to validate sessions/permissions. - -They remain under the `/auth/api/` prefix even when the auth UI moves to `/` on the auth host. - -#### Session validation & permission checks - -`POST /auth/api/validate` -- Purpose: validate the current session cookie and optionally renew it (sliding expiry). -- Query: `perm=` (repeatable) — all listed permission IDs are required. -- Response: JSON with `valid`, `user_uuid`, `renewed`. - -`GET /auth/api/forward` -- Purpose: “forward auth” endpoint for reverse proxies (Caddy/Nginx). Returns `204` with `Remote-*` headers on success. -- Query: - - `perm=` (repeatable): required permissions. - - `max_age=`: require recent authentication (e.g. `5m`, `1h`, `30s`). -- Response: - - success: `204 No Content` + headers - - failure: `4xx` with either JSON or HTML depending on `Accept`. - -See [Headers.md](Headers.md) for the `Remote-*` header contract. - -#### Client configuration - -`GET /auth/api/settings` -- Purpose: fetch runtime configuration for the frontend (RP ID/name, base paths, auth-host mode). - -#### Reset / link flows - -`GET /auth/api/token-info?token=` -- Purpose: validate a reset token and return minimal metadata for the reset UI. -- Notes: invalid or expired tokens return `404`. - -`POST /auth/api/user-info` -- Purpose: fetch user info. -- Modes: - - session mode: uses auth cookie - - reset mode: pass `reset=` (for reset/device-add flows) - -#### Session cookie management - -`POST /auth/api/logout` -- Purpose: delete the current session (best-effort) and clear the auth cookie. - -`POST /auth/api/set-session` -- Purpose: exchange a Bearer session token for an auth cookie. -- Auth: `Authorization: Bearer `. - ---- - -### 2) Auth‑host‑only API (not reachable on other hosts): `/auth/api/user/*` - -These endpoints are still “app API”, but are intentionally blocked on non‑auth hosts when `--auth-host` is configured. - -Base prefix: `/auth/api/user` - -`PUT /auth/api/user/display-name` -- Body: JSON `{ "display_name": "…" }` - -`POST /auth/api/user/logout-all` -- Purpose: terminate all sessions for the user; clears current cookie. - -`DELETE /auth/api/user/session/{session_id}` -- Purpose: terminate a specific session for the current user. - -`DELETE /auth/api/user/credential/{uuid}` -- Purpose: delete a credential (requires recent authentication). - -`POST /auth/api/user/create-link` -- Purpose: create a device addition link (reset token) for the current user (requires recent authentication). - ---- - -### 3) Auth‑host‑only admin API (not reachable on other hosts): `/auth/api/admin/*` - -Base prefix: `/auth/api/admin` - -UI root (served by the admin sub-app): -- `GET /auth/api/admin/` (serves the admin SPA HTML) - -Organizations: -- `GET /auth/api/admin/orgs` -- `POST /auth/api/admin/orgs` -- `PUT /auth/api/admin/orgs/{org_uuid}` -- `DELETE /auth/api/admin/orgs/{org_uuid}` -- `POST /auth/api/admin/orgs/{org_uuid}/permission` -- `DELETE /auth/api/admin/orgs/{org_uuid}/permission` - -Roles: -- `POST /auth/api/admin/orgs/{org_uuid}/roles` -- `PUT /auth/api/admin/orgs/{org_uuid}/roles/{role_uuid}` -- `DELETE /auth/api/admin/orgs/{org_uuid}/roles/{role_uuid}` - -Users: -- `POST /auth/api/admin/orgs/{org_uuid}/users` -- `PUT /auth/api/admin/orgs/{org_uuid}/users/{user_uuid}/role` -- `POST /auth/api/admin/orgs/{org_uuid}/users/{user_uuid}/create-link` -- `GET /auth/api/admin/orgs/{org_uuid}/users/{user_uuid}` -- `PUT /auth/api/admin/orgs/{org_uuid}/users/{user_uuid}/display-name` -- `DELETE /auth/api/admin/orgs/{org_uuid}/users/{user_uuid}/credentials/{credential_uuid}` -- `DELETE /auth/api/admin/orgs/{org_uuid}/users/{user_uuid}/sessions/{session_id}` - -Permissions (global): -- `GET /auth/api/admin/permissions` -- `POST /auth/api/admin/permissions` -- `PUT /auth/api/admin/permission` -- `POST /auth/api/admin/permission/rename` -- `DELETE /auth/api/admin/permission` - ---- - -## WebSocket endpoints - -All WebSocket endpoints are mounted under `/auth/ws/` and are **auth‑host‑only** when `--auth-host` is configured. - -### Passkey WebAuthn - -`WS /auth/ws/authenticate` -- Purpose: authenticate using a passkey. -- Response includes `session_token` (use `POST /auth/api/set-session` to set the cookie). - -`WS /auth/ws/register` -- Purpose: register a new credential. -- Query: - - `reset=` (optional): allow registration via reset link - - `name=` (optional) - -### Remote authentication (cross-device) - -Mounted under `/auth/ws/remote-auth`: - -`WS /auth/ws/remote-auth/request` -- Purpose: create a pairing code and wait for approval. - -`WS /auth/ws/remote-auth/permit` -- Purpose: approve/deny a pairing code from another device. - ---- - -## User‑navigable endpoints (browser) - -These are the URLs a user should be able to open directly. - -`GET /` and `GET /auth/` -- Serves the user profile app. - -`GET /admin/` (and legacy `/auth/admin/`) -- Admin UI (may redirect to the auth host in dedicated auth-host mode). - -`GET /{token}` (and legacy `/auth/{token}`) -- Reset / device addition UI for a single path segment token. -- Token format is validated; malformed tokens return `404`. - -`GET /auth/restricted/` -- Special HTML UI used for iframe-based authentication flows (primarily via `/auth/api/forward`). +| Path | Used for | Notes | +|---|---|---| +| `WS /auth/ws/authenticate` | Passkey authentication | Returns a `session_token` that can be exchanged via `POST /auth/api/set-session` | +| `WS /auth/ws/register` | Register a new credential | Optional query: `reset=`, `name=` | +| `WS /auth/ws/remote-auth/request` | Start a cross-device login/registration request | Returns a pairing code | +| `WS /auth/ws/remote-auth/permit` | Approve/deny a pairing code | Used from an already-authenticated device | diff --git a/Caddy.md b/Caddy.md index 813f6bd..f8438c4 100644 --- a/Caddy.md +++ b/Caddy.md @@ -1,36 +1,39 @@ +# Paskia ## Caddy configuration -We provide a few Caddy snippets that make the configuration easier, although the `forward_auth` directive of Caddy can be used directly as well. Place the auth folder with the snippets where your Caddyfile is. +We provide a few Caddy snippets that make the configuration easier, although the `forward_auth` directive of Caddy can be used directly as well. Place the [auth folder](caddy/auth) with the snippets `require` and `setup` where your config file is (e.g. `/etc/caddy/auth`) What these snippets do -- Mount the auth UI at `/auth/` proxying to `:4401` (auth backend) -- Use the forward-auth interface `/auth/api/forward` to verify the required credentials +- `setup`: Mount the auth UI at `/auth/` proxying to `:4401` +- `require`: Use `/auth/api/forward` for access control - Render a login page or a permission denied page if needed (without changing URL) Your backend may not use authentication at all, or it can make use of the user information passed via `Remote-*` headers by the authentication system, see [Headers.md](Headers.md) for details. -### 1) Protect the full site (auth/all) - -Use this when you want “login required everywhere” which is useful to protect some service that doesn't have any authentication of its own: +We assume the normal unprotected **Caddyfile** for your site looks like this: ```caddyfile -localhost { - import auth/all "" { - reverse_proxy :3000 # your app +app.example.com { + @public path /.well-known/* /favicon.ico + handle @public { + root * /var/www/ + file_server + } + + handle { + reverse_proxy :3000 # Your app backend } } ``` -The auth/all protects the entire site with a simple directive. Put your normal setup inside the block. In this example we don't require any permissions, only that the user is logged in. Instead of `""` you may specify `perm=myapp:login` or other permissions. +Note: We use the `handle @name` approach rather than `handle_path` to keep the path unaltered. Unlike bare directives, these blocks will be tried in sequence and each can contain what you'd typically put in your site definition (by default `reverse_proxy` takes precedence and nothing reaches the static files). -It is possible to add your own `handle @matcher` blocks prior importing `auth/all` for endpoints that don't require authentication, e.g. to exclude `/favicon.ico`. +We will adapt from this to protect your app. -### 2) Different areas, different permissions (auth/setup, auth/require) - -When you need a more fine-grained control, use the auth/setup and auth/require snippets: +### Protect your site (auth/setup, auth/require) ```caddyfile -localhost { +app.example.com { import auth/setup @public path /.well-known/* /favicon.ico @@ -45,20 +48,33 @@ localhost { reverse_proxy :3000 } - # Anywhere else, require login only handle { - import auth/require "" + import auth/require max-age=12h reverse_proxy :3000 } } ``` -Note: We use the `handle @name` approach rather than `handle_path` to prevent the matched path being removed out of upstream URL. Unlike bare directives, these blocks will be tried in sequence and each can contain what you'd typically put in your site definition. +The above setup allows unauthenticated access to certain files, then implements two different access controls for your backend app depending on which path is accessed. Note that the perm and max-age options may be combined, e.g. ``perm=myapp:admin&max-age=5min` on a very sensitive endpoint. This will require additional authentication if the passkey hasn't been used in the last 5 minutes (automatic session renewals don't affect this). Use `""` if you only want the user to be authenticated with no time or perm requirements. ---- +### Dedicated Authentication Site -## Override the auth backend URL (AUTH_UPSTREAM) +When you setup a separate subdomain for the authentication site, just add to your config another section for the auth host: -By default, the auth service is contacted at localhost port 4401 ("for authentication required"). You can point Caddy to a different by setting the `AUTH_UPSTREAM` environment variable for Caddy. +``` +auth.example.com { + reverse_proxy :4401 +} +``` + +Remember to specify `paskia serve --auth-host auth.example.com` to restrict the authentication services to this domain. + +Note that we still reserve `/auth/` on each site for logout page and any APIs your application may require, while full user profile and global options are only available on the auth host. + +Paskia does not require CORS configuration, but it can access the authentication and registration of auth host WS API from the other sites as WebSockets don't require any CORS. + +### Override the paskia backend address (AUTH_UPSTREAM) + +By default, the auth service is contacted at localhost port 4401. You can point Caddy to a different address by setting the `AUTH_UPSTREAM` environment variable for Caddy. If unset, the snippets use `:4401` by default. diff --git a/Headers.md b/Headers.md index d10869e..ea3129b 100644 --- a/Headers.md +++ b/Headers.md @@ -1,16 +1,17 @@ +# Paskia ## Trusted Headers for Apps | HTTP Header | Meaning | Example | |---|---|---| -| `Remote-User` | Authenticated user UUID | Unique string | -| `Remote-Name` | User display name | `John Doe` | -| `Remote-Org` | Organization UUID | Identifier for org (string) | +| `Remote-User` | Authenticated user UUID | **01c03276-b8f0-**… (string) | +| `Remote-Name` | User display name | **John Doe** | +| `Remote-Org` | Organization UUID | Identifier for user's org (string) | | `Remote-Org-Name` | Organization display name | `The Company Ltd.` | | `Remote-Role` | Role UUID | Identifier for user's role (string) | | `Remote-Role-Name` | Role display name | `Employee` | -| `Remote-Groups` | Permissions the user has, separated by commas | `auth:admin,yourapp:reports` | +| `Remote-Groups` | Permissions the user has, comma separated | `auth:admin,yourapp:reports` | | `Remote-Session-Expires` | Session expiry timestamp (ISO 8601 UTC) | `2030-12-31T23:59:59Z` | -| `Remote-Credential` | Credential UUID | Identifier for the currently signed in passkey (string) | +| `Remote-Credential` | Credential UUID | Identifier for the sign-in passkey (string) | Similar headers are also used by other authentication systems like [Authelia](https://www.authelia.com/integration/trusted-header-sso/introduction/) to signal the backend application information about the signed in user. @@ -18,4 +19,4 @@ When a request is allowed, the auth service adds these headers by the forward-au Only the UUID values should be used for identification needs, because they never change, even when things are renamed (display names change), and are never reused (created on authentication server). They are UUIDv7 so you can also extract the creation timestamp from them. -Any `Remote-*` headers from clients are stripped by our [Caddy configuration](Caddy.md) so that apps can trust these values. +Any `Remote-*` headers from clients are stripped by our [Caddy configuration](Caddy.md) to avoid dealing with any fake headers. diff --git a/README.md b/README.md index 0c30ffc..8e97908 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,8 @@ paskia serve [options] | --origin ** | Explicitly list the domain names served () | **https://**** | | --auth-host ** | Dedicated authentication site (e.g., **auth.example.com**) | Unspecified: we use **/auth/** on *every* site under rp-id.| -## Documentation +## Further Documentation - [API reference](https://git.zi.fi/LeoVasanko/paskia/src/branch/main/API.md) - [Caddy configuration](https://git.zi.fi/LeoVasanko/paskia/src/branch/main/Caddy.md) -- [HTTP headers passed to your application](https://git.zi.fi/LeoVasanko/paskia/src/branch/main/Headers.md) +- [Trusted Headers for Apps](https://git.zi.fi/LeoVasanko/paskia/src/branch/main/Headers.md)