4.6 KiB
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_PASSWORDis retired. Removed from docker-compose and the README. An existing deployment that upgrades sees the setup screen on next visit (itsuserstable 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:<salt hex>:<hash hex>"
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:<salt>:<hash>; 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? }—configurednow 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 whenidis the caller./api/settingskeeps its exact shape: GET merges the global rows with the current user's non-NULLtheme/weight_unitoverrides; 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
AuthProvidergains 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.