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:
- Detect no users exist.
- Create the admin user with the provided credentials (
is_admin = true, system admin). - Create the shared Default workspace and add the admin as workspace admin.
- 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.
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.
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 admin | Workspace owner | |
|---|---|---|
| Stored as | users.is_admin = TRUE | workspace_memberships.role = 'admin' |
| Scope | Whole installation (System Settings, SMTP, grant/revoke system admin) | One workspace (delete workspace, transfer tokens, org-scoped audit for that workspace) |
| Dashboard | System Settings nav (when the session user is admin) | Workspaces owner actions (rename, delete, transfer tokens) |
| How to grant | Workspaces > Members > System admin toggle (system admins only) | Automatic when creating a workspace; not assignable from the Members tab |
| How many | Multiple system admins supported | One 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 (
adminmembership 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
adminmembership 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):
- Pending invitations are accepted first.
- Otherwise, if the Default workspace already exists, they join it.
- If exactly one workspace exists (legacy installs), they join that workspace.
- 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
- Log in to the dashboard.
- Go to Workspaces, select a workspace, then Members.
- Invite by email with role Viewer or Manager.
- 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
- Receive the invitation email (or a link shared by the admin).
- Click the invitation link:
https://your-instance.com/auth/verify-email/<token>. - Set a password (min 8 characters, uppercase and number required by default).
- The account is created and automatically added to the workspace with the assigned role.
If SMTP is not configured, share invitation links via a secure channel manually. Invitations expire after 7 days by default. See Configuration for SMTP setup.
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:
- Use the password reset flow (requires SMTP to be configured).
- 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
| Feature | Status |
|---|---|
| Admin bootstrap (env vars) | Enabled by default |
| User invitations (admin only) | Always available |
| Local email/password auth | Enabled by default |
| Two-factor authentication (TOTP) | Enabled by default |
| CSRF protection | Enabled by default |
| Email verification | Configurable (REQUIRE_EMAIL_VERIFICATION) |
All auth tuning variables are optional with sensible defaults:
LOCAL_AUTH_ENABLED=true
REQUIRE_EMAIL_VERIFICATION=true
TWO_FACTOR_ENABLED=true
SESSION_MAX_AGE=86400000 # 24h in ms
MIN_PASSWORD_LENGTH=8
REQUIRE_UPPERCASE=true
REQUIRE_NUMBERS=true
CSRF_ENABLED=true
See the Configuration Reference for the full authentication and security variable tables.
Best practices
- Use a strong
ADMIN_PASSWORDand remove it from env after first login. - Use a secrets manager or Helm
existingSecretfor credentials. - Invite users with the least-privileged role (Viewer by default).
- Encourage all users to enable 2FA.
- Review the audit log for invitation history.