Some checks failed
CI / ci (push) Failing after 11s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
282 lines
5.7 KiB
Markdown
282 lines
5.7 KiB
Markdown
# 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.
|