# 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.

Source: https://docs.3am.si/access/sign-in · Markdown: https://docs.3am.si/access/sign-in.md · All docs: https://docs.3am.si/llms.txt
3AM is an on-prem AI on-call engineer for regulated enterprises (https://3am.si).

| Way                           | For                                                               | Set up in                                          |
| ----------------------------- | ----------------------------------------------------------------- | -------------------------------------------------- |
| **Single sign-on (OIDC)**     | Your identity provider: Entra ID, Okta, Ping, Keycloak, ADFS      | Settings → How people sign in ([SSO](https://docs.3am.si/access/sso)) |
| **Directory password (LDAP)** | LDAP or Active Directory, where no identity provider is reachable | The same                                           |
| **E-mailed code**             | Everyone else, and people added by hand                           | A mail provider: Resend or SMTP                    |
| **A password**                | One person who can't receive a code                               | Team → 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:

```bash
curl -s -X POST -H "Authorization: Bearer $ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"email":"ana@bank.example","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                                                                                              |
| ----------------- | ------------------------------------------------------------------------------------------------- |
| Codes             | Valid 10 minutes, used once, 5 tries, at most 3 per address per 10 minutes; only a hash is stored |
| Unknown addresses | The same answer whether or not the address has access, and nothing is sent                        |
| Rate limits       | 3 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`.

| Install       | Where the token is                                                                      |
| ------------- | --------------------------------------------------------------------------------------- |
| Single server | Printed by the installer once; in the data volume at `admin-token`                      |
| Kubernetes    | `kubectl -n bank-ops get secret threeam-admin -o jsonpath='{.data.token}' \| base64 -d` |
| Your own      | `THREEAM_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`):

| Rule                        | Effect                                                                                                                |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Allowed domains             | Only these domains (and subdomains). A new install starts with the first owner's domain, unless that's public webmail |
| Known disposable domains    | 85 throwaway services (mailinator, yopmail, 10minutemail, …): always refused                                          |
| Blocked domains / addresses | Whatever an admin adds                                                                                                |
| Block public webmail        | Optional: 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 <verify@3am.si>`; 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.

| Provider                                                             | Configure with                                                                                                                                                                                                         |
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Resend                                                               | Installer `--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

| Call                                                             | Who            | Purpose                                                                                                  |
| ---------------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------- |
| `POST /v1/auth/request {email}`                                  | anyone         | Send a code                                                                                              |
| `POST /v1/auth/verify {email, code}`                             | anyone         | → `{token, user}`                                                                                        |
| `POST /v1/auth/password {email, password}`                       | anyone         | Sign in with a password                                                                                  |
| `POST /v1/auth/logout`                                           | signed in      | End this session                                                                                         |
| `POST /v1/login {token}`                                         | anyone         | The 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 token    | Create the first owner (once)                                                                            |
| `GET`/`PUT /v1/settings/mail`, `POST /v1/settings/mail/test`     | viewer / admin | The provider and sender; test                                                                            |
| `GET`/`PUT /v1/settings/access`                                  | viewer / admin | Who 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`.
