# Accounts and sign-in

People sign in with an email and password; the API answers with a session cookie, `us_session` (HttpOnly, Secure,
SameSite=Lax, 30 days, extended while in use). Programs use [API tokens](tokens.md) instead. Every error is
`application/problem+json` with a `title` that says what to do. The full reference is
`GET /v1/openapi.json` (OpenAPI 3.1, generated from the routes the API serves).

The examples below run, in this order, in the API's test suite. They keep the cookie in `cookies.txt` with curl's
`-c` (save) and `-b` (send).

| Route | What it does |
|---|---|
| `POST /v1/auth/signup` | Create an account and its workspace; emails a confirmation link (24 hours) |
| `POST /v1/auth/verify` | Confirm the email address with the token from that link |
| `POST /v1/auth/verify/resend` | Send a new confirmation link |
| `POST /v1/auth/login` | Sign in; sets the cookie, or asks for a second factor |
| `POST /v1/auth/login/2fa` | The second step: the authenticator code |
| `POST /v1/auth/logout` | End this session |
| `GET /v1/auth/sessions` | Your live sessions |
| `DELETE /v1/auth/sessions/{id}` | End one of them |
| `POST /v1/auth/password/change` | Change your password (keeps this session, ends the others) |
| `POST /v1/auth/password/reset-request` | Email a reset link (1 hour) |
| `POST /v1/auth/password/reset` | Set a new password with that link; signs out everywhere |
| `POST /v1/auth/2fa/enroll` | Start two-factor sign-in (TOTP) |
| `POST /v1/auth/2fa/confirm` | Turn it on with a code |
| `POST /v1/auth/2fa/disable` | Turn it off (password and code) |
| `GET /v1/me` | Who you are: your account, workspaces, role and scopes |
| `GET /v1/openapi.json` | The API reference |

## Create an account

Passwords need at least 10 characters, and one that appears in a known data breach (checked against Have I Been
Pwned without sending the password) is refused. The answer is the same whether or not the email already has an
account.

```sh
curl https://api.usherstats.com/v1/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "ana@example.com", "password": "plum-orbit-71-lantern", "workspaceName": "Ana Sites"}'
```
<!-- expect 202 -->

The email holds a link to `https://app.usherstats.com/verify?token=...`. The app posts that token here with the password
the account was signed up with (so a link opened by someone who did not sign up confirms nothing); it works once, and
starts no session: sign in afterwards.

```sh
curl https://api.usherstats.com/v1/auth/verify \
  -H "Content-Type: application/json" \
  --data @- <<EOF
{"token": "$VERIFY_TOKEN", "password": "plum-orbit-71-lantern"}
EOF
```
<!-- expect 200 -->

Lost the email? Ask for another (always `202`, so it does not reveal who has an account):

```sh
curl https://api.usherstats.com/v1/auth/verify/resend \
  -H "Content-Type: application/json" \
  -d '{"email": "ana@example.com"}'
```
<!-- expect 202 -->

## Sign in

Until the address is confirmed, sign-in answers `403` with type `email-unverified`. After five wrong passwords for
one account from one address, sign-ins to that account from that address wait 30 seconds, doubling with each further
failure up to 15 minutes; meanwhile they get the same `401` as a wrong password, and other addresses are not
affected. The owner gets one email an hour about it. A password reset clears it at once.

```sh
curl https://api.usherstats.com/v1/auth/login -c cookies.txt \
  -H "Content-Type: application/json" \
  -d '{"email": "ana@example.com", "password": "plum-orbit-71-lantern"}'
```
<!-- expect 200 -->

```sh
curl https://api.usherstats.com/v1/me -b cookies.txt
```
<!-- expect 200 -->

`/v1/me` lists your workspaces. If you belong to more than one, choose the one a request acts on with the
`X-Workspace: ws_...` header (or `?workspace=ws_...`); without it, the oldest membership is used.

## Sessions

```sh
curl https://api.usherstats.com/v1/auth/sessions -b cookies.txt
```
<!-- expect 200 -->

```sh
curl -X POST https://api.usherstats.com/v1/auth/password/change -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{"currentPassword": "plum-orbit-71-lantern", "newPassword": "cedar-quartz-58-harbor"}'
```
<!-- expect 200 -->

## Two-factor sign-in

Enrolling returns a secret and an `otpauth://` URI for an authenticator app (show the URI as a QR code). It is not on
until a code from the app is confirmed. The secret is stored encrypted; each code works once.

```sh
curl -X POST https://api.usherstats.com/v1/auth/2fa/enroll -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{"password": "cedar-quartz-58-harbor"}'
```
<!-- expect 200; TOTP_SECRET=.secret -->

```sh
curl -X POST https://api.usherstats.com/v1/auth/2fa/confirm -b cookies.txt \
  -H "Content-Type: application/json" \
  --data @- <<EOF
{"code": "$TOTP_CODE"}
EOF
```
<!-- expect 200 -->

Sign out, then sign in again: now the password step returns a `challenge` instead of a cookie, and the code
completes it (five guesses, five minutes).

```sh
curl -X POST https://api.usherstats.com/v1/auth/logout -b cookies.txt -c cookies.txt
```
<!-- expect 204 -->

```sh
curl https://api.usherstats.com/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "ana@example.com", "password": "cedar-quartz-58-harbor"}'
```
<!-- expect 200; CHALLENGE=.challenge -->

```sh
curl https://api.usherstats.com/v1/auth/login/2fa -c cookies.txt \
  -H "Content-Type: application/json" \
  --data @- <<EOF
{"challenge": "$CHALLENGE", "code": "$TOTP_CODE"}
EOF
```
<!-- expect 200 -->

```sh
curl -X POST https://api.usherstats.com/v1/auth/2fa/disable -b cookies.txt \
  -H "Content-Type: application/json" \
  --data @- <<EOF
{"password": "cedar-quartz-58-harbor", "code": "$TOTP_CODE"}
EOF
```
<!-- expect 200 -->

## Forgotten password

Always `202`. The link (`https://app.usherstats.com/reset?token=...`) works once, for one hour. Setting the new
password signs out every session.

```sh
curl https://api.usherstats.com/v1/auth/password/reset-request \
  -H "Content-Type: application/json" \
  -d '{"email": "ana@example.com"}'
```
<!-- expect 202 -->

```sh
curl https://api.usherstats.com/v1/auth/password/reset \
  -H "Content-Type: application/json" \
  --data @- <<EOF
{"token": "$RESET_TOKEN", "password": "violet-anchor-93-meadow"}
EOF
```
<!-- expect 200 -->

```sh
curl https://api.usherstats.com/v1/me -b cookies.txt
```
<!-- expect 401 -->

Sign in with the new password, and end that session by its id:

```sh
curl https://api.usherstats.com/v1/auth/login -c cookies.txt \
  -H "Content-Type: application/json" \
  -d '{"email": "ana@example.com", "password": "violet-anchor-93-meadow"}'
```
<!-- expect 200 -->

```sh
curl https://api.usherstats.com/v1/auth/sessions -b cookies.txt
```
<!-- expect 200; SESSION_ID=.sessions[0].id -->

```sh
curl -X DELETE https://api.usherstats.com/v1/auth/sessions/$SESSION_ID -b cookies.txt -c cookies.txt
```
<!-- expect 204 -->

## The reference

```sh
curl https://api.usherstats.com/v1/openapi.json
```
<!-- expect 200 -->

## Limits

Sign-up, sign-in, the confirmation and reset emails are rate limited per address (and the emails per recipient);
an API token may make 600 requests a minute. Past a limit the answer is `429` with `Retry-After` in seconds.
