Sign-in

How people sign in (single sign-on, a directory password, or an e-mailed code), the first owner, the admin token and break-glass, who may sign in, passwords for one person, setting up e-mail, sessions.

WayForSet up in
Single sign-on (OIDC)Your identity provider: Entra ID, Okta, Ping, Keycloak, ADFSSettings → How people sign in (SSO)
Directory password (LDAP)LDAP or Active Directory, where no identity provider is reachableThe same
E-mailed codeEveryone else, and people added by handA mail provider: Resend or SMTP
A passwordOne person who can't receive a codeTeam → Password (owners)

Every tier has all of them. The admin token is not a way to sign in.

The first owner

A new install's sign-in page asks for the admin token (printed by the installer) and the first owner's e-mail, plus optionally a Resend key and the sender. That creates the owner, once:

curl -s -X POST -H "Authorization: Bearer $ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","name":"Ana"}' $URL/v1/auth/bootstrap

E-mailed codes

  1. The person enters their e-mail; 3AM sends a 6-digit code through the mail provider.
  2. They enter the code and get a session.
Rule
CodesValid 10 minutes, used once, 5 tries, at most 3 per address per 10 minutes; only a hash is stored
Unknown addressesThe same answer whether or not the address has access, and nothing is sent
Rate limits3 codes per address, 20 requests per client address, per 10 minutes

The admin token

After the first owner, it has two uses:

  • Automation: Authorization: Bearer <admin token> works on the API (scripts, CI), attributed to admin-token, with owner rights.
  • Break-glass: if codes can't be sent (no mail provider, or it refuses or is unreachable), the sign-in page offers the token. Each use is logged (identity.break_glass, with the reason), the console shows a banner until e-mail works, and the path closes when a send succeeds again.

Otherwise POST /v1/login with the token answers 403 use_email.

InstallWhere the token is
Single serverPrinted by the installer once; in the data volume at admin-token
Kuberneteskubectl -n bank-ops get secret threeam-admin -o jsonpath='{.data.token}' | base64 -d
Your ownTHREEAM_ADMIN_TOKEN, or Helm adminToken.existingSecret

Who may sign in

Only people an admin has added (or your directory's mapped groups) get in. On top of that, Team → Who may sign in (PUT /v1/settings/access):

RuleEffect
Allowed domainsOnly these domains (and subdomains). A new install starts with the first owner's domain, unless that's public webmail
Known disposable domains85 throwaway services (mailinator, yopmail, 10minutemail, …): always refused
Blocked domains / addressesWhatever an admin adds
Block public webmailOptional: Gmail, Outlook, Yahoo, iCloud, Proton and similar

Enforced when someone is added, when a code is asked for and checked, and on every request of an open session: tightening the policy signs people now outside it out at once. A policy that would shut out every owner is refused.

A password, for one person

For a reviewer, an auditor or a guest with no mailbox on your domain:

  • Who: anyone but an owner, and nobody from the directory. Only an owner gives, replaces or removes one.
  • The password: 12+ characters (the console proposes one), shown once, kept as an scrypt hash, never in the log.
  • An end: 7, 30 (default) or 90 days, or none. A session opened with it ends with it.
  • Guessing: five wrong tries lock an address for 15 minutes; 30 tries from one client in 10 minutes are refused; every failure gets the same answer.
  • Signing in: I was given a password on the sign-in page (POST /v1/auth/password). Off when the install is single sign-on only.

Setting up e-mail

The sender must be on a domain the provider has verified (default 3AM <[email protected]>; set yours in Settings → E-mail sign-in, Helm mail.from or THREEAM_MAIL_FROM). Resend's sandbox sender delivers only to the Resend account's own address.

ProviderConfigure with
ResendInstaller --resend-key-file FILE; Helm mail.resendKeySecret; THREEAM_RESEND_KEY_FILE; or the console (first owner, or Settings). Saved to secrets/resend.key (0600), never returned
SMTP (SES, Postmark, SendGrid, Workspace, Microsoft 365, your relay)Installer --smtp-host H [--smtp-port 587] [--smtp-user U --smtp-password-file F] [--smtp-security starttls|ssl|none]; Helm mail.smtp.*; THREEAM_SMTP_*; or the console. THREEAM_SMTP_HOST alone selects SMTP

Send me a test e-mail (Settings) proves it before anyone needs a code. When the provider fails, nobody is locked out: break-glass opens until a send succeeds.

Sessions

A random 256-bit token, only its hash stored, kept in the browser tab's sessionStorage (gone when the tab closes). A session ends after 12 hours idle or 7 days, at sign-out, or when the person is disabled or their role changes. Every change is recorded under the signed-in person's e-mail; a by a page sends is ignored.

API

CallWhoPurpose
POST /v1/auth/request {email}anyoneSend a code
POST /v1/auth/verify {email, code}anyone→ {token, user}
POST /v1/auth/password {email, password}anyoneSign in with a password
POST /v1/auth/logoutsigned inEnd this session
POST /v1/login {token}anyoneThe admin token: {bootstrap: true} on a new install, {break_glass: true} when mail fails, else 403
POST /v1/auth/bootstrap {email, name, resend_key?, mail_from?}admin tokenCreate the first owner (once)
GET/PUT /v1/settings/mail, POST /v1/settings/mail/testviewer / adminThe provider and sender; test
GET/PUT /v1/settings/accessviewer / adminWho may sign in

Audit events: identity.signed_in, identity.code_sent, identity.code_failed, identity.code_wrong, identity.code_ignored, identity.code_refused, identity.code_rate_limited, identity.access_changed, identity.break_glass, identity.token_sign_in_refused, identity.password_set, identity.password_removed, identity.password_wrong, config.mail_saved, config.mail_tested.

On this page