style: format docs, fix user_permissions variable scoping in auth middleware

Apply ruff line-wrapping formatting to docs and test files.
Clarify auth middleware: extract user_permissions once before
subsystem check, removing conditional variable scoping.
This commit is contained in:
2026-07-27 18:37:11 +00:00
parent d4213fb93b
commit ca110c321d
9 changed files with 640 additions and 26 deletions
+355 -1
View File
@@ -1,9 +1,24 @@
# REST API Reference
All endpoints are served by the management WebUI Flask application bound to `127.0.0.1:9090`, proxied through nginx with SSL termination and HTTP basic authentication. Requests target the management domain (e.g., `https://<hostname>.local/api/...`).
All endpoints are served by the management WebUI Flask application bound to `127.0.0.1:9090`, proxied through nginx with SSL termination. Authentication is handled at the Flask layer via JWT — the `Authorization: Bearer <token>` header. Public endpoints (login, WebAuthn authenticate) do not require a token.
Every request and response uses `Content-Type: application/json`.
## Authentication
Most endpoints require a valid JWT access token. The token is obtained by logging in via `POST /api/auth/login` or completing a WebAuthn authentication ceremony.
### Obtaining a Token
1. Call `POST /api/auth/login` with credentials
2. Store the returned `access_token`
3. Include `Authorization: Bearer <access_token>` on all subsequent requests
4. Refresh before expiry via `POST /api/auth/refresh`
### Permission Checks
Each request is checked against per-subsystem permissions. `GET` requires `"read"` or `"rw"` on the subsystem. `POST`/`PATCH`/`DELETE` requires `"rw"`. User management endpoints (`/api/auth/users/*`) require `auth: "rw"`.
## Conventions
### Success Responses
@@ -41,6 +56,345 @@ Resource identification uses **path parameters** whenever possible. Exceptions o
---
## Auth API
Endpoints prefixed with `/api/auth/...`. Manage authentication, session, tokens, and user accounts.
### Login
#### Password Login
```
POST /api/auth/login
```
Authenticate with username and password. Returns access and refresh tokens.
**Auth:** Public — no JWT required.
**Request Body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `username` | `string` | Yes | Username |
| `password` | `string` | Yes | Plain-text password (hashed for verification) |
**Response (`data`):**
| Field | Type | Description |
|---|---|---|
| `tokens` | `object` | Contains `access_token`, `refresh_token`, and `session_id` |
| `access_ttl` | `integer` | Access token lifetime in seconds (default: `900`) |
| `user` | `object` | User info (`username`, `id`) |
| `permissions` | `object` | Per-subsystem permissions |
Returns HTTP `401` if credentials are invalid.
---
### Session
#### Get Current Session
```
GET /api/auth/session
```
Return the current authenticated user and permissions.
**Auth:** Access token required.
**Response (`data`):**
| Field | Type | Description |
|---|---|---|
| `user` | `object` | User info (`username`, `id`) |
| `permissions` | `object` | Per-subsystem permissions |
Returns HTTP `401` if token is invalid, expired, or blacklisted.
#### Logout
```
POST /api/auth/logout
```
Invalidate the current session by blacklisting the access token.
**Auth:** Access token required.
**Response:** `data` is `null` on success.
#### Refresh Tokens
```
POST /api/auth/refresh
```
Rotate token pair. Validates the refresh token, blacklists the old pair, and issues new access and refresh tokens.
**Auth:** Refresh token required.
**Response (`data`):**
| Field | Type | Description |
|---|---|---|
| `tokens` | `object` | Contains `access_token`, `refresh_token`, and `session_id` |
| `access_ttl` | `integer` | Access token lifetime in seconds |
| `user` | `object` | User info (`username`, `id`) |
| `permissions` | `object` | Per-subsystem permissions |
Returns HTTP `401` if refresh token is invalid, expired, or blacklisted.
#### Change Password
```
POST /api/auth/password
```
Change the current user's password.
**Auth:** Access token required.
**Request Body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `username` | `string` | No | Auto-injected from JWT context |
| `oldPassword` | `string` | Yes | Current password |
| `newPassword` | `string` | Yes | New password |
**Response:** `data` is `null` on success.
Returns HTTP `400` if old password is incorrect.
---
### User Management
#### List Users
```
GET /api/auth/users
```
List all users. Requires admin permission (`auth: "rw"`).
**Auth:** `auth: "rw"` required.
**Response (`data`):**
| Field | Type | Description |
|---|---|---|
| `users` | `[object, ...]` | Array of user summaries (`id`, `username`, `permissions`) |
#### Create User
```
POST /api/auth/users
```
Create a new user with password and per-subsystem permissions.
**Auth:** `auth: "rw"` required.
**Request Body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `username` | `string` | Yes | Username |
| `password` | `string` | Yes | Plain-text password |
| `permissions` | `object` | Yes | Per-subsystem permissions (`{ subsystem: "read" \| "rw" }`) |
**Response:** `data` is `null` on success.
Returns HTTP `409` if username already exists.
#### Update User
```
POST /api/auth/users/<username>
```
Update user's permissions. (To change a password, use `POST /api/auth/password`.)
**Auth:** `auth: "rw"` required.
**Request Body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `permissions` | `object` | No | New per-subsystem permissions |
**Response:** `data` is `null` on success.
Returns HTTP `404` if user not found.
#### Delete User
```
DELETE /api/auth/users/<username>
```
Delete a user and all associated permissions and WebAuthn credentials (CASCADE).
**Auth:** `auth: "rw"` required. Cannot delete self.
**Response:** `data` is `null` on success.
Returns HTTP `404` if user not found.
---
### WebAuthn
#### Begin Registration
```
POST /api/auth/webauthn/register-begin
```
Start WebAuthn credential registration. Returns options for `navigator.credentials.create()`.
**Auth:** Access token required.
**Request Body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `username` | `string` | Yes | Username to register for |
**Response (`data`):**
| Field | Type | Description |
|---|---|---|
| `challenge` | `string` | Base64url challenge |
| `rp` | `object` | Relying party config (`id`, `name`) |
| `user` | `object` | User info for registration |
| `excludeCredentials` | `[object, ...]` | Credentials to exclude |
#### Finish Registration
```
POST /api/auth/webauthn/register-finish
```
Complete WebAuthn credential registration. Verifies the attestation response and stores the credential in the database.
**Auth:** Access token required.
**Request Body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `username` | `string` | Yes | Username |
| `response` | `object` | Yes | WebAuthn authenticator attestation response |
| `name` | `string` | No | Display name for this credential |
**Response:** `data` is `null` on success.
Returns HTTP `400` if verification fails.
#### Begin Authentication
```
POST /api/auth/webauthn/authenticate-begin
```
Start WebAuthn authentication. Returns options for `navigator.credentials.get()`.
**Auth:** Public — no JWT required.
**Request Body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `username` | `string` | Yes | Username to authenticate |
**Response (`data`):**
| Field | Type | Description |
|---|---|---|
| `challenge` | `string` | Base64url challenge |
| `allowCredentials` | `[object, ...]` | Registered credentials for this user |
Returns `{"ok": true, "data": {"no_webauthn": true}}` if user has no WebAuthn credentials (use password instead).
#### Finish Authentication
```
POST /api/auth/webauthn/authenticate-finish
```
Complete WebAuthn authentication. Verifies the assertion and issues tokens on success.
**Auth:** Public — no JWT required.
**Request Body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `username` | `string` | Yes | Username |
| `assertion_response` | `object` | Yes | WebAuthn authenticator assertion response |
| `auth_options` | `object` | Yes | Original authentication options from `authenticate-begin` |
**Response (`data`):**
| Field | Type | Description |
|---|---|---|
| `tokens` | `object` | Contains `access_token`, `refresh_token`, and `session_id` |
| `access_ttl` | `integer` | Access token lifetime in seconds |
| `user` | `object` | User info (`username`, `id`) |
| `permissions` | `object` | Per-subsystem permissions |
Returns HTTP `400` if verification fails.
#### List Credentials
```
GET /api/auth/webauthn/credentials
```
List WebAuthn credentials for the current user.
**Auth:** Access token required.
**Response (`data`):**
Array of credential objects (`id`, `name`, `transports`, `credentialId`, `signCount`, `createdAt`).
#### Credential Counts
```
GET /api/auth/webauthn/credential-counts
```
Return credential counts for all users. Admin endpoint.
**Auth:** `auth: "rw"` required.
**Response (`data`):**
| Field | Type | Description |
|---|---|---|
| `counts` | `object` | Dict mapping usernames to credential counts (`{"alice": 2, "bob": 1}`) |
#### Remove Credential
```
DELETE /api/auth/webauthn/creds/<credential_id>
```
Remove a WebAuthn credential.
**Auth:** Access token required.
**Response:** `data` is `null` on success.
Returns HTTP `404` if credential not found.
---
## Firewall API
Endpoints prefixed with `/api/firewall/...`. Interact with firewalld for zone management, rich rules, NAT, and masquerade.