# 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.