docs: add authentication, API reference, and MCP server guides
Some checks failed
CI / ci (push) Failing after 11s
Some checks failed
CI / ci (push) Failing after 11s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
281
docs/authentication.md
Normal file
281
docs/authentication.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# Authentication
|
||||
|
||||
GearBox uses a public-read, authenticated-write model. All GET endpoints are publicly accessible with no credentials required. Any request that modifies data (POST, PUT, PATCH, DELETE) requires authentication.
|
||||
|
||||
This is a single-user app. There is exactly one admin account.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [First-Time Setup](#first-time-setup)
|
||||
- [Web UI Authentication](#web-ui-authentication)
|
||||
- [API Keys](#api-keys)
|
||||
- [Auth Middleware Behavior](#auth-middleware-behavior)
|
||||
- [Auth API Reference](#auth-api-reference)
|
||||
- [Frontend Behavior](#frontend-behavior)
|
||||
|
||||
---
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
When no users exist, all write endpoints return `403` with `{ "error": "setup_required" }`. To create the admin account, visit `/login` in the browser and complete the setup form, or call the setup endpoint directly:
|
||||
|
||||
```http
|
||||
POST /api/auth/setup
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "yourpassword"
|
||||
}
|
||||
```
|
||||
|
||||
Requirements:
|
||||
- `username`: any non-empty string
|
||||
- `password`: minimum 6 characters
|
||||
|
||||
This endpoint only works when no users exist. Subsequent calls return `403 { "error": "Setup already completed" }`.
|
||||
|
||||
On success, a session cookie is set and `201` is returned:
|
||||
|
||||
```json
|
||||
{ "username": "admin" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Web UI Authentication
|
||||
|
||||
Sessions use an `httpOnly` cookie named `gearbox_session`.
|
||||
|
||||
| Property | Value |
|
||||
|------------|--------------------|
|
||||
| Cookie name | `gearbox_session` |
|
||||
| httpOnly | true |
|
||||
| sameSite | Lax |
|
||||
| path | / |
|
||||
| Max age | 30 days |
|
||||
|
||||
The session expiry is **automatically refreshed** on each authenticated request. As long as the app is used at least once every 30 days, the session stays active.
|
||||
|
||||
Passwords are hashed with **argon2** via `Bun.password`.
|
||||
|
||||
### Changing Your Password
|
||||
|
||||
Requires an active session cookie.
|
||||
|
||||
```http
|
||||
PUT /api/auth/password
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"currentPassword": "oldpassword",
|
||||
"newPassword": "newpassword"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Keys
|
||||
|
||||
API keys are intended for programmatic access (scripts, MCP clients, integrations). They are managed under **Settings > API Keys** in the web UI, or via the API endpoints listed below.
|
||||
|
||||
### Key behavior
|
||||
|
||||
- Keys are shown **once** at creation time. Store them securely.
|
||||
- Keys are stored as an argon2 hash. Only the 8-character prefix is stored in plaintext for display and lookup purposes.
|
||||
- Pass the key via the `X-API-Key` request header on any write request.
|
||||
|
||||
```http
|
||||
POST /api/items
|
||||
X-API-Key: gbk_a1b2c3d4...
|
||||
Content-Type: application/json
|
||||
|
||||
{ "name": "Revelate Tangle", "categoryId": 2 }
|
||||
```
|
||||
|
||||
If both a session cookie and an `X-API-Key` header are present, the API key is checked first.
|
||||
|
||||
---
|
||||
|
||||
## Auth Middleware Behavior
|
||||
|
||||
The middleware applied to `/api/*` (excluding `/api/auth/*`) follows these rules:
|
||||
|
||||
1. `GET` requests — always allowed, no auth check.
|
||||
2. No users exist — returns `403 { "error": "setup_required" }`.
|
||||
3. `X-API-Key` header present — verified against stored hashes; `401` on failure.
|
||||
4. `gearbox_session` cookie present — verified against sessions table; refreshed on success; `401` on failure.
|
||||
5. Neither credential present — returns `401 { "error": "Authentication required" }`.
|
||||
|
||||
The `/api/auth/*` routes handle their own auth logic and are excluded from the global middleware.
|
||||
|
||||
---
|
||||
|
||||
## Auth API Reference
|
||||
|
||||
### `GET /api/auth/me`
|
||||
|
||||
Returns the current session state. Always public.
|
||||
|
||||
**Response when logged in:**
|
||||
```json
|
||||
{
|
||||
"user": { "id": 1 },
|
||||
"setupRequired": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response when logged out, setup complete:**
|
||||
```json
|
||||
{
|
||||
"user": null,
|
||||
"setupRequired": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response when no users exist:**
|
||||
```json
|
||||
{
|
||||
"user": null,
|
||||
"setupRequired": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/auth/setup`
|
||||
|
||||
Create the first admin account. Only works when no users exist.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "yourpassword"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `201`
|
||||
```json
|
||||
{ "username": "admin" }
|
||||
```
|
||||
|
||||
Sets `gearbox_session` cookie.
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/auth/login`
|
||||
|
||||
Log in with username and password.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "yourpassword"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `200`
|
||||
```json
|
||||
{ "username": "admin" }
|
||||
```
|
||||
|
||||
Sets `gearbox_session` cookie. Returns `401` on invalid credentials.
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/auth/logout`
|
||||
|
||||
Clear the current session. No request body needed.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{ "ok": true }
|
||||
```
|
||||
|
||||
Clears the `gearbox_session` cookie and deletes the session from the database.
|
||||
|
||||
---
|
||||
|
||||
### `PUT /api/auth/password`
|
||||
|
||||
Change the admin password. Requires an active session cookie (not API key).
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"currentPassword": "oldpassword",
|
||||
"newPassword": "newpassword"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{ "ok": true }
|
||||
```
|
||||
|
||||
Returns `401` if `currentPassword` is incorrect.
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/auth/keys`
|
||||
|
||||
List all API keys. Returns name, prefix, and creation timestamp — never the full key.
|
||||
|
||||
Requires auth.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Claude Code",
|
||||
"prefix": "gbk_a1b2",
|
||||
"createdAt": "2025-03-01T10:00:00.000Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/auth/keys`
|
||||
|
||||
Create a new API key. The full key is returned **once** and cannot be retrieved again.
|
||||
|
||||
Requires auth.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{ "name": "Claude Code" }
|
||||
```
|
||||
|
||||
**Response:** `201`
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Claude Code",
|
||||
"key": "gbk_a1b2c3d4e5f6g7h8i9j0...",
|
||||
"prefix": "gbk_a1b2"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `DELETE /api/auth/keys/:id`
|
||||
|
||||
Revoke an API key by ID. Requires auth.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{ "ok": true }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Behavior
|
||||
|
||||
- A login button is shown in the top-right corner of the UI (Gitea-style).
|
||||
- The floating action button (FAB) for adding items is hidden when not logged in.
|
||||
- Edit and delete actions on items, threads, and setups require auth. Unauthenticated users see read-only views.
|
||||
- When `setupRequired` is true, the UI redirects to the setup flow.
|
||||
Reference in New Issue
Block a user