Built-in admin login
A self-hosted control plane can verify operators in one of two ways, selected
by SCRIBASE_AUTH_MODE:
| Mode | What signs you in | Default on |
|---|---|---|
builtin |
The control plane's own password login (argon2id, optional TOTP) | The single-box layout |
oidc |
An external identity provider with RFC 7662 token introspection (SCRIBASE_OIDC_*) |
Everything else |
Personal access tokens, organization API keys and organization SSO sessions
work in both modes. This page covers builtin.
Create the first administrator
Pick one of two options.
Option A: setup token (no secret in the env file). Leave
SCRIBASE_ADMIN_EMAIL and SCRIBASE_ADMIN_PASSWORD_HASH blank. While no
administrator exists, every boot prints a one-time setup token to the
control-API log. Spend it from the CLI:
docker compose -p scribase logs control-api | grep -A1 'setup token'
scribase admin setup --email you@example.com --setup-token scb_setup_...or open the console: its sign-in page shows a Setup token form instead of
the password form until an administrator exists. Setting only
SCRIBASE_ADMIN_EMAIL binds the token to that address.
Option B: pre-seed the administrator. Generate a password hash and put it in the env file:
scribase admin hash-password # prompts twice, prints an argon2id PHC stringSCRIBASE_ADMIN_EMAIL=you@example.com
SCRIBASE_ADMIN_PASSWORD_HASH='$argon2id$v=19$m=19456,t=2,p=1$...'Wrap the hash in single quotes so Docker Compose keeps every $. On boot
the account is created or promoted. Changing the hash later resets that
administrator's password once, on the next boot. That is the break-glass path
if you are locked out.
Sign in
- Console: when the control plane can send email (see Sign in with an email link), the sign-in page leads with Email me a sign-in link; Use a password instead shows the password form. After either, an authentication code follows if the account has an authenticator app. Use a recovery code instead accepts one of the one-time recovery codes.
- CLI:
scribase login --email you@example.comprompts for the password (echo off, never from arguments) and then for a code if one is enrolled. See Login and tokens.
Security properties
- Passwords: argon2id (19 MiB, t=2), at least 12 characters. An unknown
account and a wrong password answer the same
401after the same work. - Lockout and rate limits: 10 failed passwords within 15 minutes lock the
account for 15 minutes (
SCRIBASE_LOGIN_MAX_FAILURES,SCRIBASE_LOGIN_LOCKOUT_SECONDS). Each client address is also limited to 20 sign-in attempts per 5 minutes. - Two-factor: enroll an authenticator app under Account > Security.
TOTP secrets are sealed with
SCRIBASE_KMS_MASTER_KEY. Regenerate recovery codes on the same page. - Sessions: opaque
scb_ses_bearers, stored only as SHA-256 digests, with an idle timeout (default 2 hours), an absolute timeout (default 12 hours) and rotation every 15 minutes (SCRIBASE_SESSION_IDLE_SECONDS,SCRIBASE_SESSION_ABSOLUTE_SECONDS,SCRIBASE_SESSION_ROTATE_SECONDS). You can list and sign out sessions under Account > Security > Sessions. - Audit: every sign-in, failure, lockout, sign-out and password change is
recorded (
GET /v1/auth/builtin/events, administrators only).
Sign in with an email link
When email is configured (webhook or box SMTP, as above), members can sign in without a password. They enter their email and get a message with a single-use link and a 6-digit code (for signing in on another device). Both expire in 15 minutes; asking again replaces the earlier ones, and a code allows at most 5 guesses.
- The link opens
SCRIBASE_CONSOLE_URL/auth/magic?token=scb_mgl_..., which only previews the link; the member clicks Continue to sign in, so mail scanners that open every URL cannot use it up. - The answer to a request is the same whether or not the address has an account. Requests are limited to 5 per 5 minutes per client address and per email, plus one email per address per minute.
- A new address gets its account when the link is redeemed, if sign-up is open
(
SCRIBASE_PUBLIC_SIGNUP); the organization card check still applies before the first project. - The operator administrator never gets a link: a request for that address sends a notice to sign in with the password and second factor instead.
- API:
POST /v1/auth/builtin/magic-link/request{"email"},.../magic-link/preview{"token"},.../magic-link/verify{"token"}or{"email","code"}. Table: control-plane migration0030_builtin_magic_links.sql.
Invite members
An organization owner or admin invites people by email from the console. The invitee opens the link, sets a password and joins with the invited role. The normal organization roles (owner, admin, developer, viewer) apply.
Reset a forgotten password
Reset links are single use and stored only as SHA-256 digests. Creating a new link replaces the account's earlier unused ones. Setting the new password clears any lockout and signs out every session of the account. It does not sign the member in: they sign in normally afterwards, so an authenticator app still applies.
- Administrator-issued (always available): under Account > Security >
Reset a member's password, the operator administrator enters the member's
email and their own password, and gets a link valid for 24 hours to share.
API:
POST /v1/auth/builtin/password-resetswith{"email", "admin_password"}. - Emailed (when a mail relay is configured): set
SCRIBASE_MAIL_WEBHOOK_URLto anhttps://relay that accepts{to, subject, text, html}JSON (bearerSCRIBASE_MAIL_WEBHOOK_TOKEN), the same relay that emails invitations. The sign-in page then offers Forgot your password?, which emails a link valid for 1 hour. The answer is the same whether or not the email has an account, and each address gets at most one email per minute. Without a relay, the page tells the member to ask the administrator. Instead of a webhook you can use the box SMTP relay: whenSCRIBASE_MAIL_WEBHOOK_URLis unset andSCRIBASE_DEFAULT_SMTP_HOSTplusSCRIBASE_DEFAULT_SMTP_ADMIN_EMAILare set (the single-box compose passes them fromSMTP_*in.env), account email goes out over SMTP (port 465 implicit TLS, any other port STARTTLS, required).
Links open SCRIBASE_CONSOLE_URL/reset-password/<token>, so set
SCRIBASE_CONSOLE_URL on the control API. The reset table comes from
control-plane migration 0023_builtin_password_resets.sql.
The administrator cannot create a link for their own account. If the
administrator is locked out, set a new SCRIBASE_ADMIN_PASSWORD_HASH and
restart (see above).
Console configuration
Build the console with SCRIBASE_AUTH_MODE=builtin and give its server
SCRIBASE_API_INTERNAL_URL (for example http://127.0.0.1:8081). The console
server keeps the session in a __Host- cookie (HttpOnly, Secure,
SameSite=Lax) and proxies /v1 calls, checking origin and a CSRF token on
every mutation. The bearer never reaches browser script. Set
SCRIBASE_CONSOLE_URL to the console's public origin. For plain-HTTP local
development only, set SCRIBASE_CONSOLE_INSECURE_COOKIES=1.
Automation
A CLI session ends with the timeouts above. For CI and scripts, create a
personal access token with scribase token create or an organization API key
(see Access tokens & API keys).