# Auth and mail

> Passwords, codes by email, two factors, password reset, email change, sign in alerts, the rate limit and the mails, all declared in schema.ts.

An auth collection is a collection whose records sign in. Every option of it lives in `schema.ts`, beside its fields. The server checks the options at boot, as PocketBase does, and refuses a combination that cannot work.

## Options in schema.ts

```ts
import { auth, bool, mailTemplatesFr, text } from 'locosdk/schema'

const fr = mailTemplatesFr({ otpSeconds: 600 })

users: auth({
    fields: { name: text(), role: text(), disabled: bool() },
    indexes: ['CREATE UNIQUE INDEX idx_users_name ON users (name)'],
    authRule: 'disabled = false',
    options: {
        passwordAuth: { identityFields: ['email', 'name'] },
        otp: { enabled: true, duration: 600, length: 6, emailTemplate: fr.otp },
        mfa: { enabled: true, rule: "role = 'admin'" },
        authAlert: { enabled: true },
        authToken: { duration: 7 * 86400 },
        resetPasswordTemplate: fr.resetPassword,
    },
}),
```

- Every key is optional. A key left out is the default, so removing a line turns the option back to its default.
- A schema never holds a token secret: the type has no slot for one. Secrets are minted once in the database and survive every boot.
- The boot refuses: two factors with fewer than two methods on, an identity field without a single column unique index, a mail template with an empty subject or body, a duration outside its range.

## Defaults

| what | default |
|---|---|
| auth token | 12 hours, refreshable |
| file token | 180 seconds |
| verification link | 1 day |
| password reset link, email change link | 30 minutes |
| code by email | 8 digits, 180 seconds |
| two factor session | 600 seconds |
| password | 8 characters minimum, 71 maximum, bcrypt cost 10 |

## Signing in

- **Password**: `POST /api/collections/users/auth-with-password` with `identity` and `password`. The identity fields are `email` by default.
- **Code by email**: `request-otp` mails a code and answers an `otpId`, also for an unknown address, so nobody can probe accounts. `auth-with-otp` signs in with it. Wrong codes are limited to 5 per 180 seconds per account.
- **Two factors**: with `mfa.enabled`, a first factor answers 401 `{ mfaId }` when `mfa.rule` matches the record. The second factor sends that `mfaId` with a DIFFERENT method. The client's `authWithPassword` returns `{ mfaId }` in its type, and `Session` opens the code step by itself ([Client and data layer](https://golocomotive.fr/docs/developers/client.md#the-second-factor-is-in-the-type)).
- **The auth rule** runs after the factors: `authRule: 'disabled = false'` refuses a switched off account with 403.
- **Superusers** sign in on `_superusers`. `golo superuser upsert <email>` creates one or sets its password, from stdin.

## Changing a password or an email

- A user changing their own password sends `oldPassword`. Without it, or with a wrong one, the update fails: a stolen session cannot set a new password.
- `request-password-reset` mails a link (at most one per 2 minutes) and answers the same whether the address exists or not. `confirm-password-reset` sets the password and ends every session of the account.
- `request-email-change` mails a link to the NEW address. `confirm-email-change` takes the link's token and the account password, sets the email and `verified`, and ends every session.
- A password or email change rotates the record's `tokenKey`. Every token of the record dies at once, codes and MCP access tokens included.

## Sign in alerts

With `authAlert.enabled` (the default), a password or code sign in from a new device mails the account an alert with the time, the IP and the browser. The first sign in only records its device. An account keeps its 5 newest devices. A failed alert is logged and never blocks the sign in.

## The rate limit

Password and code sign ins are limited to 5 per 3 seconds per client IP, then 429 `Too Many Requests.` Behind a proxy, set `trustedProxy` so the limit sees the real client and not the proxy ([Files, backups and proxies](https://golocomotive.fr/docs/developers/files-backups.md#behind-a-proxy)). `createApp({ rateLimits })` takes PocketBase's rate limit rules for other labels.

## Mail

```ts
import { createApp, smtpTransport } from 'locosdk/server'

const app = createApp({
    schema,
    meta: { appName: 'Orders', appURL: 'https://orders.example.com', senderName: 'Orders', senderAddress: 'no-reply@example.com' },
    mail: smtpTransport({ host: 'smtp.example.com', port: 587, username: 'orders', password: process.env.SMTP_PASSWORD ?? '' }),
})
```

- The SMTP client is built in, with no dependency: STARTTLS when the server offers it, implicit TLS with `tls: true`, AUTH PLAIN or LOGIN. It refuses to send a password over plain text, except to the local machine.
- `smtpProbe(config)` connects, authenticates and quits: `{ ok: true }` or `{ ok: false, error }`, never the password.
- Five mails per auth collection: `otp`, `authAlert`, `resetPassword`, `verification`, `confirmEmailChange`. English is PocketBase's text. French comes from `mailTemplatesFr({ otpSeconds })`.
- Placeholders: `{APP_NAME}`, `{APP_URL}`, `{TOKEN}`, `{OTP}`, `{OTP_ID}`, `{ALERT_INFO}`, and `{RECORD:<field>}` for any field that is not hidden.
- Every mail goes through `onMailerSend`, where a hook can add a footer or change the recipients.

## Not here yet

> **Planned.** OAuth2 sign in with an outside provider, email verification as a sign in requirement, impersonation and listing the auth methods are not built. A syntax check of `mfa.rule` at boot is not built either: a bad rule fails at sign in.
