Three independent feature specs covering: - API endpoint for fetching images from URLs with local storage - Public-read/authenticated-write auth with sessions and API keys - Built-in MCP server for Claude Code/Desktop integration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.6 KiB
4.6 KiB
Authentication — Design Spec
Overview
Add authentication to GearBox with a public-read, authenticated-write model. Web UI uses cookie-based sessions. Programmatic access (MCP server, scripts) uses API keys. Single-user app — one admin account, created on first setup.
Database Schema
users table
export const users = sqliteTable("users", {
id: integer("id").primaryKey({ autoIncrement: true }),
username: text("username").notNull().unique(),
passwordHash: text("password_hash").notNull(),
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
});
sessions table
export const sessions = sqliteTable("sessions", {
id: text("id").primaryKey(), // random token
userId: integer("user_id").notNull().references(() => users.id),
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
});
apiKeys table
export const apiKeys = sqliteTable("api_keys", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
keyHash: text("key_hash").notNull(),
keyPrefix: text("key_prefix").notNull(), // first 8 chars for identification
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
});
Password Hashing
Use Bun.password.hash() and Bun.password.verify() with argon2 (Bun's default). No external dependencies needed.
Auth Middleware
Hono middleware applied to all write endpoints (POST, PUT, DELETE):
- Check for
X-API-Keyheader — if present, hash and compare againstapi_keystable - Check for session cookie (
gearbox_session) — if present, look up insessionstable, verify not expired - If neither is valid, return
401 Unauthorized
GET endpoints remain public — no middleware applied.
Exempt from auth: /api/auth/login, /api/auth/setup, and /api/auth/me (GET) are not protected by the write middleware.
Before setup: If no user account exists yet, write endpoints return 403 with { error: "setup_required" } so the frontend can prompt account creation.
API Endpoints
Auth routes (/api/auth)
POST /api/auth/login— accepts{ username, password }, creates session, sets cookiePOST /api/auth/logout— clears session cookie, deletes session recordGET /api/auth/me— returns current user info if authenticated, ornullPOST /api/auth/setup— initial account creation (only works if no users exist)PUT /api/auth/password— change password (requires current password)
API key routes (/api/auth/keys) — all authenticated
GET /api/auth/keys— list API keys (name, prefix, createdAt — never the full key)POST /api/auth/keys— create new key, returns the full key onceDELETE /api/auth/keys/:id— revoke a key
Session Management
- Session token: 32-byte random hex string
- Cookie:
gearbox_session, httpOnly, sameSite=lax, path=/ - Session expiry: 30 days, refreshed on each authenticated request
- Sessions stored in SQLite — simple cleanup of expired rows
Frontend Changes
Login button (top-right, Gitea-style)
- When not logged in: "Sign in" button in the header/navbar
- When logged in: username display with dropdown (logout, settings link)
Login page
- Route:
/login - Simple form: username + password + submit
- Redirects back to previous page on success
- Error message on invalid credentials
Initial setup
- If
GET /api/auth/mereturnsnulland no users exist, show a setup prompt - Setup form: create username + password
- Only shown once — after account creation, normal login flow applies
Conditional UI
- Add/edit/delete buttons: hidden when not authenticated
- Forms (ItemForm, CandidateForm, etc.): only accessible when authenticated
- The app is fully browseable without login — you just can't modify anything
Auth state
useAuthhook using React Query: callsGET /api/auth/me- Returns
{ user, isAuthenticated, login, logout } - Cached and invalidated on login/logout
Settings page additions
- API key management section: list, create, revoke
- Change password form
Testing
- Auth service tests: login, logout, session creation/validation, password change
- API key tests: create, verify, revoke
- Middleware tests: write endpoints reject without auth, read endpoints work without auth
- Setup flow test: first-user creation
Security Considerations
- Passwords hashed with argon2 via Bun built-in
- Session tokens are cryptographically random
- API keys hashed before storage (only shown once on creation)
- httpOnly cookies prevent XSS access to session
- No CORS changes needed (single-origin app)