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:
+355
-1
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user