Skip to main content
Version: 0.11

Authentication

Admin bootstrap

TokenTimer creates the first admin user automatically on first startup. Before the first start, set the bootstrap environment variables:

ADMIN_EMAIL=admin@company.com
ADMIN_PASSWORD=YourSecurePassword123!
ADMIN_NAME=Administrator

On first startup, TokenTimer will:

  1. Detect no users exist.
  2. Create the admin user with the provided credentials (is_admin = true, system admin).
  3. Create the shared Default workspace and add the admin as workspace admin.
  4. Log the admin credentials (email shown, password hidden).

The bootstrap is not an HTTP endpoint. It runs internally on API startup and is:

  • Skipped if any user already exists in the database.
  • Skipped if DISABLE_ADMIN_BOOTSTRAP=true.
warning

After first login, remove ADMIN_PASSWORD from your .env file (or Helm values) and restart services. It is only needed for the bootstrap on first start.

note

When deploying via the Helm chart, config.adminEmail is required. The admin password is auto-generated if not provided; retrieve it from the Kubernetes secret after install (see the install runbook).

Disabling the bootstrap

Set DISABLE_ADMIN_BOOTSTRAP=true to skip automatic admin creation and create users manually via SQL:

INSERT INTO users (email, password_hash, display_name, auth_method, email_verified)
VALUES ('admin@company.com', '$2b$12$...', 'Admin', 'local', TRUE);

System admin vs workspace owner

TokenTimer separates installation-wide administration from workspace ownership. They use different database fields and are granted through different paths.

System adminWorkspace owner
Stored asusers.is_admin = TRUEworkspace_memberships.role = 'admin'
ScopeWhole installation (System Settings, SMTP, grant/revoke system admin)One workspace (delete workspace, transfer tokens, org-scoped audit for that workspace)
DashboardSystem Settings nav (when the session user is admin)Workspaces owner actions (rename, delete, transfer tokens)
How to grantWorkspaces > Members > System admin toggle (system admins only)Automatic when creating a workspace; not assignable from the Members tab
How manyMultiple system admins supportedOne owner per workspace in normal operation (creator at provision time)

Granting system admin to a second user does not make them workspace owner on the shared Default workspace. They remain manager there unless they separately created that workspace.

For the day-to-day role model (Admin, Workspace Manager, Viewer) and permission boundaries, see Teams, Workspaces, and RBAC.

Granting a second system admin

A system admin can open Workspaces > Members, find any member, and enable System admin. That sets users.is_admin = TRUE only. The member keeps their current workspace role (typically manager on Default). They gain System Settings and admin API access immediately (the session reloads is_admin on each request).

The last system admin cannot demote themselves.

What you cannot do from the UI

  • Invite someone as workspace owner (admin membership is rejected by the API).
  • Promote an existing member to workspace owner via the role dropdown (only Viewer and Manager).
  • Demote or remove a workspace owner via the Members tab (the API blocks changes to admin membership rows).

Adding another workspace owner currently requires a direct database change; the product intentionally supports a single owner per workspace until co-owner management ships.

Default workspace join semantics

When a user without workspace membership logs in or registers (and is not joining via invitation):

  1. Pending invitations are accepted first.
  2. Otherwise, if the Default workspace already exists, they join it.
  3. If exactly one workspace exists (legacy installs), they join that workspace.
  4. Otherwise, a new Default workspace is created.

Join role rules:

  • Join role is always workspace_manager when Default already exists, even if users.is_admin = TRUE.
  • Join role is admin (workspace owner) only when this login creates a brand-new Default workspace (empty install).

Invitation flow

For admins and workspace managers

  1. Log in to the dashboard.
  2. Go to Workspaces, select a workspace, then Members.
  3. Invite by email with role Viewer or Manager.
  4. To grant system admin (installation-wide access to System Settings and admin APIs), toggle System admin on an existing member. Only current system admins see this control.

If the email belongs to an existing user, they are added to the workspace directly. If not, an invitation token is created and (optionally) emailed.

For invited users

  1. Receive the invitation email (or a link shared by the admin).
  2. Click the invitation link: https://your-instance.com/register?token=<token>&email=<email>.
  3. Set a password meeting all five password requirements.
  4. The account is created and automatically added to the workspace with the assigned role.
note

If SMTP is not configured, share invitation links via a secure channel manually. See Configuration for SMTP setup.

Invitation links do not expire

There is no expiry on invitation tokens. A link stays redeemable until it is accepted or explicitly cancelled, so treat it as a live credential: share it over a secure channel, and cancel invitations you no longer expect to be accepted (DELETE /api/v1/workspaces/:id/invitations/:invitationId, or the Members tab) instead of waiting for a timeout that never comes.

Password reset and recovery

Users can reset their own passwords only if SMTP is configured. Otherwise, an admin must reset via the database or re-invite the user.

Forgotten admin password

The bootstrap only runs when no users exist, so setting ADMIN_PASSWORD again will not help. Options:

  1. Use the password reset flow (requires SMTP to be configured).
  2. Reset via the database:

Generate a bcrypt hash (cost 12):

node -e "require('bcryptjs').hash('NewPassword123!',12).then(h=>console.log(h))"

Then update the user row:

UPDATE users SET password_hash = '<hash from above>' WHERE email = 'admin@company.com';

Feature summary and tuning

FeatureStatus
Admin bootstrap (env vars)Enabled by default
User invitations (admin only)Always available
Local email/password authAlways available (not configurable)
Two-factor authentication (TOTP)Always available, opt-in per user
CSRF protectionAlways on (not configurable)
Email verificationEnforced for local accounts only
There is no auth tuning surface today

Earlier versions of this page listed LOCAL_AUTH_ENABLED, REQUIRE_EMAIL_VERIFICATION, TWO_FACTOR_ENABLED, SESSION_MAX_AGE, MIN_PASSWORD_LENGTH, REQUIRE_UPPERCASE, REQUIRE_NUMBERS and CSRF_ENABLED as optional tuning variables. Setting them has no effect. They are parsed into an internal config object that no code reads. Do not rely on them to relax or tighten authentication.

The actual, non-configurable behavior:

BehaviorActual value
Session cookie lifetime2 hours, renewed on activity (rolling)
CSRF protectionAlways on (double-submit cookie), with a fixed exempt list for machine-token routes
Local email/password authAlways available
Two-factor (TOTP)Always available, opt-in per user
Password rulesFixed, see Password requirements
Email verificationEnforced for accounts whose auth_method is local

Password requirements

Enforced server-side on invitation acceptance, password reset, and password change. All five rules are mandatory and none are configurable:

  • at least 12 characters
  • at least one lowercase letter
  • at least one uppercase letter
  • at least one number
  • at least one special character

Password reset additionally rejects passwords that start with a common sequence (password, 123456, qwerty, admin, letmein, welcome, monkey, dragon), so a password can satisfy all five rules above and still be refused on that route.

A rejected password returns 400. On invitation acceptance and password reset the body carries code: "VALIDATION_ERROR", error set to the first failing rule, and details listing every failing rule. Password change (POST /api/account/change-password) returns only error, set to a single fixed message listing the rules, with no code or details.

See the Configuration Reference for the full authentication and security variable tables.

Best practices

  • Use a strong ADMIN_PASSWORD and remove it from env after first login.
  • Use a secrets manager or Helm existingSecret for credentials.
  • Invite users with the least-privileged role (Viewer by default).
  • Encourage all users to enable 2FA.
  • Review the audit log for invitation history.