# Feature: User Accounts & Onboarding Replaces the single shared-password auth (`AUTH_PASSWORD` env var, in-memory session Set) with real user accounts. Decided with the user on 2026-07-02. ## Scope decisions (user-confirmed) | Question | Decision | |---|---| | Data scope | **Shared herd** — users are separate logins to the same rabbitry. No ownership columns on rabbits/litters/shows; nothing about the data model changes. | | Account creation | **In-app, no self-registration** — a fresh install shows a one-time "create your account" setup screen; after that, any signed-in user can add more users from Settings. No email/invite infrastructure. | | Roles | **Everyone equal** — no admin flag. Any user can manage users and settings. (Trust model: family/partners on a LAN.) | | Onboarding extras | **Per-user preferences only** — `theme` and `weight_unit` become per-user; `rabbitry_name`, `kit_prefix`, `default_breed` (and the internal `kit_counter`) stay global. No first-run settings wizard, no guided tour. | ## Consequences to be aware of - **Auth becomes mandatory.** Today, unset `AUTH_PASSWORD` = open access. After this feature, a fresh install requires creating an account, and every request requires a session. The "no password = open" mode is retired. - **`MYKINDLEBOX_PASSWORD` / `AUTH_PASSWORD` is retired.** Removed from docker-compose and the README. An existing deployment that upgrades sees the setup screen on next visit (its `users` table is empty); herd data is untouched. The old env var is simply ignored. - Because you must be signed in to delete a user and you **cannot delete yourself**, the last account can never be deleted — no lockout state exists. ## Data model ``` users: id integer PK autoincrement username text NOT NULL UNIQUE COLLATE NOCASE password_hash text NOT NULL -- "scrypt::" theme text -- NULL = inherit global settings value weight_unit text -- NULL = inherit global settings value created_at text NOT NULL default current_timestamp auth_sessions: token text PK -- randomUUID user_id integer NOT NULL FK -> users.id ON DELETE CASCADE expires_at text NOT NULL -- ISO; 30 days from login created_at text NOT NULL default current_timestamp ``` Sessions move from the in-memory Set to SQLite so a server restart/redeploy no longer signs everyone out. Expired rows are deleted lazily whenever a session check touches them. Deleting a user cascades their sessions (they're signed out everywhere immediately). ## Password hashing `node:crypto` `scryptSync` (N=16384 default params), 16-byte random salt, 64-byte key, stored as `scrypt::`; verified with `timingSafeEqual`. Deliberately **not** bcrypt/argon2 — no new native dependency, so the Alpine Docker build stays exactly as it is. Failed logins get a flat ~350 ms delay as cheap brute-force damping. ## API - `GET /api/auth/status` → `{ configured, authenticated, user? }` — `configured` now means "at least one user exists". `user` = `{ id, username }` when authenticated. - `POST /api/auth/setup` `{ username, password }` — creates the **first** user and signs them in; 409 once any user exists. This is the onboarding endpoint. - `POST /api/auth/login` `{ username, password }` (username case-insensitive). - `POST /api/auth/logout` — deletes the session row. - `GET /api/users` → `[{ id, username, created_at }]` (no hashes). - `POST /api/users` `{ username, password }`. - `PATCH /api/users/:id` `{ password }` — reset any user's password (everyone equal; family trust model — also the "someone forgot theirs" path). - `DELETE /api/users/:id` — 400 when `id` is the caller. - **`/api/settings` keeps its exact shape**: GET merges the global rows with the current user's non-NULL `theme`/`weight_unit` overrides; PATCH routes those two keys to the user row and everything else to the settings table. Client consumers (ThemeSync, useWeightUnit, Settings page) need no changes for prefs. ## Client - `AuthProvider` gains a third gate: `configured: false` → `SetupScreen` (create first account), else unauthenticated → `LoginScreen` (now username + password), else app. Context exposes `{ user, logout }`. - Settings gets a **Users** card: list users, add user, reset password, delete (self-delete disabled). The Account card shows who is signed in. - Password minimum: 8 chars, enforced on both setup and user creation. ## Out of scope (deliberate) Roles/permissions, invite links, email anything, per-user data visibility, password-strength meters, 2FA, audit logs.