diff --git a/API.md b/API.md index 46191bf..a1f5c73 100644 --- a/API.md +++ b/API.md @@ -1,105 +1,203 @@ -# Paskia API Documentation +# Paskia HTTP + WebSocket surface -This document lists the HTTP and WebSocket endpoints exposed by the Paskia -service and how they behave depending on whether a dedicated authentication host -(`--auth-host` / environment `PASSKEY_AUTH_HOST`) is configured. +This document is a factual map of the URLs Paskia exposes. -## Base Paths & Host Modes +Two important distinctions: -Two deployment modes: +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). -1. Multi‑host (default – no `--auth-host` provided) - - All endpoints are reachable on any host under the `/auth/` prefix. - - A convenience root (`/`) also serves the main app. +Within **API endpoints**, the most important group comes first: -2. Dedicated auth host (`--auth-host auth.example.com`) - - The specified auth host serves the UI at the root (`/`, `/admin/`, reset tokens, etc.). - - Other (non‑auth) hosts show a lightweight account summary at `/` or `/auth/`, while other UI routes still redirect to the auth host. - - Restricted endpoints on non‑auth hosts return `404` instead of redirecting. +- **`/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. -### Path Mapping When Auth Host Enabled +## Host modes (what changes when `--auth-host` is set) -| Purpose | On Auth Host | On Other Hosts (incoming) | Action | -|---------|--------------|---------------------------|--------| -| Main UI | `/` | `/auth/` or `/` | Serve account summary SPA (no redirect) | -| Admin UI root | `/admin/` | `/auth/admin/` or `/admin/` | Redirect -> auth host `/admin/` (strip `/auth`) | -| Reset / device addition token | `/{token}` | `/auth/{token}` | Redirect -> auth host `/{token}` (strip `/auth`) | -| Static assets | `/auth/assets/*` | `/auth/assets/*` | Served directly (no redirect) | -| Unrestricted API | `/auth/api/...` | `/auth/api/...` | Served directly | -| Restricted API (admin,user,ws namespaces) | `/auth/api/{admin|user|ws}*` | same path | 404 on non‑auth hosts | -| WebSocket (register/auth) | `/auth/ws/*` | `/auth/ws/*` | 404 on non‑auth hosts | +Paskia can run in either mode: + +- **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`). + +Rules are enforced by middleware in `paskia/fastapi/auth_host.py`. + +### Summary table (dedicated auth host mode) + +| 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 | Notes: -- “Strip `/auth`” means only when the path starts with that exact segment. -- A reset token is a single path segment validated by server logic; malformed tokens 404. -- Method and body are preserved for UI redirects (307 Temporary Redirect). - -## HTTP UI Endpoints - -| Method | Path (multi‑host) | Path (auth host) | Description | -|--------|-------------------|------------------|-------------| -| GET | `/auth/` | `/` | Main authentication SPA (non-auth hosts show an account summary view) | -| GET | `/auth/admin/` | `/admin/` | Admin SPA root | -| GET | `/auth/{reset_token}` | `/{reset_token}` | Reset / device addition SPA (token validated) | - -## Core API (Unrestricted – available on all hosts) - -Always under `/auth/api/` (even on auth host): - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/auth/restricted/` | Authentication UI for iframe embedding (supports `?mode=login` or `?mode=reauth`) | -|--------|------|-------------| -| POST | `/auth/api/validate` | Validate & (conditionally) renew session | -| GET | `/auth/api/forward` | Auth proxy endpoint for reverse proxies (204 or 4xx) | -| POST | `/auth/api/set-session` | Set cookie from Bearer token | -| POST | `/auth/api/logout` | Logout current session | -| POST | `/auth/api/user-info` | Authenticated user + context info (also handles reset tokens) | -| POST | `/auth/api/create-link` | Create a device addition link (reset token) | -| DELETE | `/auth/api/credential/{uuid}` | Delete user credential | -| DELETE | `/auth/api/session/{session_id}` | Terminate a specific session | -| POST | `/auth/api/user/logout-all` | Terminate all sessions for the user | -| PUT | `/auth/api/user/display-name` | Update display name | - -## Restricted API Namespaces - -When `--auth-host` is set, requests to these paths on non‑auth hosts return 404: - -| Namespace | Examples | -|-----------|----------| -| `/auth/api/admin` | `/auth/api/admin/orgs`, `/auth/api/admin/orgs/{uuid}` ... | -| `/auth/api/user` | Segment prefix – includes `/auth/api/user/...` endpoints (logout-all, display-name, session, credential) | -| `/auth/api/ws` | (Reserved / future) | - -## WebSockets (Passkey) - -| Path | Description | Host Mode Behavior | -|------|-------------|--------------------| -| `/auth/ws/register` | Register new credential (new or existing user) | 404 on non‑auth hosts when auth host configured | -| `/auth/ws/authenticate` | Authenticate user & issue session | 404 on non‑auth hosts when auth host configured | - -## Redirection & Status Codes - -| Scenario | Response | -|----------|----------| -| UI path on non‑auth host (auth host configured) | 307 redirect to auth host; `/auth` prefix stripped | -| Reset token UI path on non‑auth host | 307 redirect (token preserved) | -| Restricted API on non‑auth host | 404 | -| Unrestricted API on any host | Normal response | -| No auth host configured | All hosts behave like multi-host mode (no redirects; everything accessible) | - -## Headers for /auth/api/forward -See `Headers.md` for details of headers returned on success (204). - -## Notes for Integrators -1. Always use absolute `/auth/api/...` paths for programmatic requests (they do not move when an auth host is introduced). -2. Bookmark / deep links to UI should resolve correctly after redirection if users access via a non-auth application host. -3. Treat 404 from restricted namespaces on non-auth hosts as a signal to direct users to the central auth site. - -## Environment & CLI Summary -| Option | Effect | -|--------|--------| -| `--auth-host` / `PASSKEY_AUTH_HOST` | Enables dedicated host mode, root-mounts UI there, restricts certain namespaces elsewhere | +- “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. --- -This document reflects current behavior of the middleware-based host routing logic. + +## 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`). diff --git a/Headers.md b/Headers.md index 668474b..d10869e 100644 --- a/Headers.md +++ b/Headers.md @@ -1,17 +1,21 @@ -## Headers your app receives +## Trusted Headers for Apps -When a request is allowed, the auth service adds these headers before proxying to your app (e.g., the service at `:3000`). Your app can use them for user context and authorization. - -| Header | Meaning | Example | +| HTTP Header | Meaning | Example | |---|---|---| -| `Remote-User` | Authenticated user UUID | `3f1a2b3c-4d5e-6789-abcd-ef0123456789` | -| `Remote-Name` | User display name | `Jane Doe` | -| `Remote-Org` | Organization UUID | `a1b2c3d4-1111-2222-3333-444455556666` | -| `Remote-Org-Name` | Organization display name | `Acme Inc` | -| `Remote-Role` | Role UUID | `b2c3d4e5-2222-3333-4444-555566667777` | -| `Remote-Role-Name` | Role display name | `Administrators` | -| `Remote-Groups` | Comma‑separated permissions the user has | `myapp:reports,auth:admin` | -| `Remote-Session-Expires` | Session expiry timestamp (ISO 8601) | `2025-09-25T14:30:00Z` | -| `Remote-Credential` | Credential UUID backing the session | `c3d4e5f6-3333-4444-5555-666677778888` | +| `Remote-User` | Authenticated user UUID | Unique string | +| `Remote-Name` | User display name | `John Doe` | +| `Remote-Org` | Organization UUID | Identifier for 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-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) | -Note: Any incoming `Remote-*` headers from clients are stripped by our [Caddy configuration](Caddy.md), so that apps can trust these values. +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. + +When a request is allowed, the auth service adds these headers by the forward-auth mechanism before proxying to your app as **request headers**. Your app can use them for user context to show on UI, or for its own authentication needs (e.g. prevent different orgs messing up with each other's data, logging which user performed an action). + +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. diff --git a/README.md b/README.md index d558f00..0c30ffc 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,9 @@ uvx paskia serve --rp-id example.com On the first run it downloads the software and prints a registration link for the Admin. If you are going to be connecting `localhost` directly, for testing, leave out the rp-id. -The server will start up on [localhost:4401](http://localhost:4401) "for authentication required", serving for `*.example.com`. +The server will start up on [localhost:4401](http://localhost:4401) *for authentication required*, serving for `*.example.com`. -Otherwise you will need a web server such as [Caddy](https://caddyserver.com/) to serve HTTPS on your actual domain names and proxy requests to Paskia and your backend apps. - -A quick example without any config file: -```fish -sudo caddy reverse-proxy --from example.com --to :4401 -``` +Otherwise you will need a web server such as [Caddy](https://caddyserver.com/) to serve HTTPS on your actual domain names and proxy requests to Paskia and your backend apps (see documentation below). For a permanent install of `paskia` CLI command, not needing `uvx`: @@ -55,15 +50,13 @@ There is no config file. Pass only the options on CLI: paskia serve [options] ``` -### Optional options - | Option | Description | Default | |--------|-------------|---------| -| Listen address | One of **[host]:port** (address and port) or **unix:/path.sock** (Unix socket) | **localhost:4401** | -| --rp-id ** | Main domain (required for production) | | -| --rp-name "**" | Name of your company or site | same as rp-id | -| --origin ** | Explicit single site | **https://**** | -| --auth-host ** | Dedicated authentication site (e.g., **auth.example.com**) | | +| Listen address | One of ****:**** (default all hosts, port 4401) or **unix:***/path/to/***paskia.socket** (Unix socket) | **localhost:4401** | +| --rp-id ** | Main/top domain | **localhost** | +| --rp-name "**" | Name of your company or site | Same as rp-id | +| --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