
# User Management

Sovrium provisions and manages users without ever requiring you to touch the database directly. The first administrator is created at boot (from environment variables, a one-time token, or the CLI); subsequent users are created, listed, role-assigned, and invited through the authenticated admin API. Every operation is RBAC-gated and audit-logged.

User management is only available when [authentication](/en/docs/auth-overview) is configured. The admin plugin is enabled automatically the moment an `auth` block exists — there is no separate flag to set.

## Admin Bootstrap

The very first admin cannot be created by another admin (there is none yet) or by self-registration (you do not want anyone claiming the admin seat). Sovrium offers **three complementary bootstrap paths** for provisioning that first account on a fresh database.

| Path                  | When to use                             | Mechanism                                                                                                                                             |
| --------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Env-var bootstrap** | Automated / IaC deploys                 | Set `AUTH_ADMIN_EMAIL` + `AUTH_ADMIN_PASSWORD` (+ optional `AUTH_ADMIN_NAME`); the admin is created on first boot.                                    |
| **One-time token**    | You do not want credentials in env vars | Boot with no `AUTH_ADMIN_EMAIL` and no users; a 64-hex token is printed in the startup banner and claimed once via `POST /api/admin/bootstrap/claim`. |
| **CLI**               | Interactive provisioning                | `sovrium admin create <email>` works against the configured database (or the default SQLite file) with no `app.yaml` required.                        |

### Env-var bootstrap (no-config)

```bash
AUTH_ADMIN_EMAIL=admin@example.com
AUTH_ADMIN_PASSWORD=SecureP@ssw0rd!
AUTH_ADMIN_NAME=System Administrator   # optional, defaults to "Administrator"
```

On first boot against a fresh database the server provisions the admin with a verified email and grants it access to every admin-only endpoint. On subsequent startups the path no-ops — it never creates a duplicate and never modifies an existing user, even if the email already maps to a different role. The success is logged **without** exposing the password.

| Env var               | Description                                                                                   |
| --------------------- | --------------------------------------------------------------------------------------------- |
| `AUTH_ADMIN_EMAIL`    | Email for the bootstrap admin. Required for the env-var path. Must be a valid email format.   |
| `AUTH_ADMIN_PASSWORD` | Initial password. Required for the env-var path. Must meet the minimum length (8 characters). |
| `AUTH_ADMIN_NAME`     | Display name. Optional — defaults to `Administrator`.                                         |

:::callout
**Bootstrap is gated on a configured `auth` block.** If `auth` is absent, or `AUTH_ADMIN_EMAIL`/`AUTH_ADMIN_PASSWORD` is missing, no admin is created — the server boots without one. The env-var path is a no-op when any user already exists.
:::

### One-time token bootstrap

When the server boots with **no** `AUTH_ADMIN_EMAIL` set **and** no users in the database, it generates a 256-bit cryptographically random token, prints it once in the startup banner, and accepts a single claim:

```bash
# Token appears in the banner as:
#   → First-admin token (POST /api/admin/bootstrap/claim): <64-hex-token>

curl -X POST http://localhost:3000/api/admin/bootstrap/claim \
  -H 'Content-Type: application/json' \
  -d '{ "token": "<64-hex-token>", "email": "admin@example.com", "password": "SecureP@ssw0rd!", "name": "Admin" }'
```

Security properties:

- Only the SHA-256 hash of the token is persisted; the plaintext is printed to stdout exactly once and never logged.
- The token expires after **1 hour** and can be claimed **once** — replays return `401`.
- Once any admin exists the route returns **404**: the bootstrap window has closed, and even a leaked valid token cannot reopen it.

This is the path that makes "run the binary on a fresh server with only `DATABASE_URL` set, open the URL, build the app live" possible. See [Database Infrastructure](/en/docs/database-infrastructure) for the zero-config database that pairs with it.

## Creating & Managing Users

Once an admin exists, all subsequent user lifecycle operations run through the admin API. Each endpoint requires an authenticated admin session — unauthenticated requests return `401`, non-admin sessions return `403`.

| Operation      | Endpoint                                   | Notes                                                                                   |
| -------------- | ------------------------------------------ | --------------------------------------------------------------------------------------- |
| Create user    | `POST /api/auth/admin/create-user`         | Engineer chooses the password; **no email** is sent. Returns `201`.                     |
| List users     | `GET /api/auth/admin/list-users`           | Paginated (`limit`/`offset`), returns count metadata, supports search by email or name. |
| Get user       | `GET /api/auth/admin/get-user/:id`         | Full detail incl. role, ban status, email-verified flag. `404` for unknown ids.         |
| Set role       | `POST /api/auth/admin/set-role`            | Assign `admin` / `member` / `viewer` (or any [custom role](/en/docs/auth-roles-rbac)).  |
| Set password   | `POST /api/auth/admin/set-user-password`   | Reset a user's password administratively.                                               |
| List sessions  | `GET /api/auth/admin/list-user-sessions`   | Active [sessions](/en/docs/auth-sessions) for a user.                                   |
| Revoke session | `POST /api/auth/admin/revoke-user-session` | Force-logout a specific session.                                                        |
| Impersonate    | `POST /api/auth/admin/impersonate-user`    | Start/stop impersonation for support workflows.                                         |

```yaml
auth:
  strategies:
    - type: emailAndPassword
  defaultRole: member
```

:::callout
**Validation is enforced server-side.** Create-user returns `400` for a missing/malformed email or missing password, and `422` when the email already exists. Roles assigned must be one of the app's declared roles — see [Roles & RBAC](/en/docs/auth-roles-rbac).
:::

## Role Assignment

Sovrium ships three default roles — `admin`, `member`, `viewer` — and supports custom roles declared in the `auth` block. New users receive `auth.defaultRole` (falls back to `member`) unless an explicit role is supplied at creation. The first bootstrap admin is always created with the `admin` role and a verified email.

Roles drive every authorization decision in the platform: table [permissions](/en/docs/table-permissions), per-field access, page access, and the admin API itself. See [Roles & RBAC](/en/docs/auth-roles-rbac) for the full role model and field-level permission matrix.

## Invitation Flow

The `create-user` endpoint requires the admin to choose the customer's password and sends no email — unsuitable for real customer onboarding. The parallel **passwordless invitation flow** closes that gap: the admin issues an invitation with `{ email, name, role }` (no password), Sovrium emails a single-use link, and the customer sets their own password and lands authenticated.

```yaml
auth:
  strategies:
    - type: emailAndPassword
  invitationTokenExpiry: '72h' # default lifetime; accepts '30s' / '15m' / '72h' / '7d' / ms
  emailTemplates:
    invitation:
      subject: 'You are invited to join, $name'
      text: |
        Hi $name,
        $inviterName invited you to join.
        Set your password: $url
        This invitation expires in 72 hours.
```

| Endpoint                                 | Behavior                                                                                                                                                                                           |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /api/auth/admin/invite-user`       | Accepts `{ email, name, role }` (no password). Returns `200` with `{ user, invitationSent: true }`. `401`/`403` for unauth/non-admin; `422` when the email already maps to a fully-onboarded user. |
| `POST /api/auth/admin/accept-invitation` | Backs the public `/accept-invitation?token=...` page. The customer sets a password and lands in an authenticated session.                                                                          |

Invitation tokens reuse the `auth.verification` table (the same shape Better Auth uses for password reset), expire after `invitationTokenExpiry` (default 72h), and are single-use — replays return `400`/`410`. Email substitution variables: `$name`, `$url`, `$email`, `$inviterName`. `auth.allowSignUp: false` does **not** block invitations — admin-driven user creation is always available when auth is configured.

:::callout
**Onboarding is decoupled from access assignment.** Inviting a user creates the account but does not grant them any tenant scope. Wiring a user to data access is a separate step via the standard records API — an admin can invite first and assign later, or vice versa.
:::

## Related Pages

- [Authentication Overview](/en/docs/auth-overview) — strategies, configuration, the `auth` block.
- [Roles & RBAC](/en/docs/auth-roles-rbac) — role model and field-level permissions.
- [Sessions](/en/docs/auth-sessions) — session lifetime, revocation, multi-device.
- [Admin Dashboard](/en/docs/admin-dashboard) — operator-grade read API over users, tables, automations.
- [Activity Monitoring](/en/docs/activity-monitoring) — audit trail of user and admin actions.
- [Database Infrastructure](/en/docs/database-infrastructure) — the zero-config database the no-config bootstrap pairs with.
- [Environment Variables](/en/docs/env-vars) — full env-var reference incl. `AUTH_ADMIN_*`.
