--- type: database title: User Table description: The `user` table — accounts, roles, and statuses. tags: [database, user, auth] timestamp: 2026-07-21T14:18:00Z --- # Schema | Column | Type | Description | |----------------|----------|------------------------------------------------------------------| | `id` | TEXT PK | UUID v4 generated on creation. | | `username` | TEXT | Unique login name. | | `password_hash`| TEXT | Argon2id hash (memory/time/parallelism from env config). | | `role` | TEXT | `'admin'` or `'player'`. Default `'player'`. | | `status` | TEXT | `'enabled'` or `'disabled'`. Default `'enabled'`. | | `created_at` | TEXT | ISO 8601 timestamp (`strftime('%Y-%m-%dT%H:%M:%fZ','now')`). | # Constraints - Unique on `username`. - `role` is enforced application-side (see `AdminService.updateUserRole`). - `status` is checked by `AuthService.login` and `AuthService.refresh` — disabled users cannot log in or refresh. # Invariants - The application enforces the **last-admin invariant**: at least one enabled user with `role = 'admin'` must always exist. Attempts to demote or delete the last admin are rejected by `UsersService.enforceLastAdminInvariant` (used by `AdminService.updateUserRole` and `AdminService.deleteUser`). # First-admin bootstrap When the `user` table is empty of admins, `POST /api/v1/auth/register-first-admin` is the only endpoint that can create one. After that, the endpoint returns `409 SYSTEM_INITIALIZED`. # See also - [Auth and Settings Tables](/database/auth-settings.md) - [REST API Overview](/api/rest-overview.md) - [Admin Endpoints](/api/admin.md)