Skip to content

Authentication and Users

diegokoes edited this page Jul 5, 2026 · 1 revision

Authentication and Users

Auth guards /api/* only. /health, the SPA shell, and /api/setup/status stay public (browsers cannot attach a bearer to asset requests, so the shell must load before the user is known). The implementation is packages/api/src/auth.ts.

Ways in

Three mechanisms, freely combined. The bearer token always works for automation independently of the interactive options.

Mode How Role Notes
Password login Setup wizard creates the first admin; users then sign in with email plus password per-user (admin / member) scrypt-hashed; session cookie signed with TACHY_SESSION_SECRET
token TACHY_API_TOKEN bearer on /api/* admin automation, scripts, CI; timing-safe comparison
sso OIDC_ISSUER / OIDC_CLIENT_ID / OIDC_CLIENT_SECRET (plus TACHY_SESSION_SECRET) per-user, member by default Microsoft Entra or any OIDC; coexists with password login
open nothing configured, wizard skipped admin no auth, binds to 127.0.0.1 only; laptop dev

authMode is auto-derived (sso if OIDC is set, else token if a bearer is set, else open) unless TACHY_AUTH_MODE overrides it. The public GET /auth/config tells the SPA which of wizard / password form / SSO button to show.

Identity resolution

resolveIdentity tries, in order:

  1. Bearer token (Authorization: Bearer ..., timing-safe) → admin, via: token.
  2. Password session cookie → looks up the user; rejected if disabled.
  3. SSO session → looks up the user (or defaults to member); rejected if disabled.
  4. Open fallback → only when no token, no OIDC, and the instance is not yet bootstrapped. Returns admin, via: open.

A null result is a 401. The resolved identity is stashed on the request and read by requireAdmin.

The setup wizard (first-run bootstrap)

"Bootstrapped" means at least one active admin exists. On first boot with an empty database:

  • GET /api/setup/status returns { bootstrapped: false }, so the SPA shows the wizard instead of a login form.
  • POST /api/setup (public, but only usable while no admin exists) creates the admin account (email, scrypt-hashed password, optional display name), writes optional org name plus runtime settings, optionally seeds a first team and product, and signs the caller in.
  • It takes an exclusive table lock on users so two concurrent first-run POSTs cannot both become admin, and it self-disables permanently once an admin exists (a second call gets 409).

Once bootstrapped it can never revert (demoting or disabling the last admin is rejected in core), so the positive result is cached for the process lifetime.

Loopback until guarded

packages/api/src/index.ts binds to all interfaces only when some auth stands guard: a token, SSO, or a bootstrapped password instance. Otherwise it stays on 127.0.0.1 and logs a warning, so an unauthenticated instance is never exposed. Run the wizard locally first (or set TACHY_API_TOKEN / OIDC_*), then restart to accept remote requests. Under Docker with no token, "loopback inside the container" means the published port is unreachable from the host until an admin exists, which is the intended safe default.

Sessions

Password and SSO sessions are signed cookies (tachy_session for password login, the OIDC middleware's own for SSO), 7-day lifetime. TACHY_SESSION_SECRET (32+ chars) signs them. If unset, a per-process random secret is generated with a loud warning and sessions reset on every restart. Set it in any real deployment.

A naive in-memory throttle limits password attempts to 5 failures per minute per email.

Roles and users

Each user has a global role: admin or member.

  • Admins manage users, org structure, and settings. Every mutation under the admin API and the whole /api/users surface requires the admin role (requireAdmin returns 403 otherwise). The bearer token and open mode act as admin.
  • Members use the app: reads plus the agent Chat.

Users are managed in Admin › Users (/api/users): create a user (with an optional initial password and role), patch role / password / disabled, and manage team membership (team_members). SSO users are upserted on first sign-in (attribution and role default member). TACHY_USER_EMAIL attributes the standalone MCP server; web and SSO users are attributed from their login instead.

Clone this wiki locally