Add two-factor login (TOTP) with recovery codes
Users can set up an authenticator app from their account page; login then asks for a code after the password, with one-time recovery codes as a fallback. Sensitive actions (tokens, user changes, 2FA resets) need a code from the last 5 minutes. totp.require (none/admins/all) forces setup, and admins can reset a user's 2FA from the UI, the jarl CLI or jarld on the server. The dashboard and jarl status flag users missing 2FA and a shared "admin" account still in use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b22ffee1bb
commit
8f9cdd0a17
35 files changed
+1965
-43
No files matched your search
+28
-2
@@ -26,7 +26,27 @@ change something (anything but GET/HEAD) must send it as `X-CSRF-Token`.
|
||||
`POST /auth/logout` ends the session.
|
||||
|
||||
`GET /auth/whoami` returns the user, the token (if any), the effective
|
||||
permissions and whether the caller is an administrator.
|
||||
permissions and whether the caller is an administrator. For sessions it
|
||||
also has `totp` (the user's two-factor login: `enabled`, `required`,
|
||||
`recovery_codes_left`) and `totp_setup_required`.
|
||||
|
||||
**Two-factor login** ([permissions.md](permissions.md#two-factor-login)):
|
||||
for a user with it, `POST /auth/login` answers a correct password with
|
||||
`{"totp_required": true, "challenge": "..."}` and no cookie. Send
|
||||
`POST /auth/login/totp` with `{"challenge": "...", "code": "123456"}` (or
|
||||
a recovery code) within 5 minutes to get the session. Wrong codes answer
|
||||
401 `totp_invalid`; after 5, or 5 minutes, `login_expired` (start again).
|
||||
|
||||
Creating or rotating tokens, changing users and resetting someone's
|
||||
two-factor login need, in a session of a user with two-factor login, a
|
||||
code proved in the last 5 minutes. Otherwise they answer 403
|
||||
`totp_step_up`: send the request again with the current code in
|
||||
`X-Jarl-TOTP: 123456` (a wrong one gives 403 `totp_invalid`). Tokens and
|
||||
the local socket never need one.
|
||||
|
||||
When `totp.require` applies to a user who hasn't set it up, a session can
|
||||
only call `/auth/whoami`, `/auth/logout`, `/me/password` and `/me/totp...`;
|
||||
everything else answers 403 `totp_setup_required`.
|
||||
|
||||
**The local socket**: on jarld's unix socket (`/run/jarl/jarl.sock`),
|
||||
requests from root or jarld's own user need no `Authorization` header and
|
||||
@@ -354,13 +374,19 @@ Success is `200 {"ok": true, "fqdn": ..., "changeset_id": ..., "zones": [...]}`.
|
||||
|--------|------|------|
|
||||
| GET | `/users` | admin |
|
||||
| POST | `/users` | `{"username": "alice", "password": "...", "admin": false, "role": "dns-editor", "grant_strings": ["records:write zones=lab.adbraun.lol"], "comment": ""}` (`password: ""` = service account) |
|
||||
| GET / PATCH / DELETE | `/users/{id}` | PATCH also takes `disabled` |
|
||||
| GET / PATCH / DELETE | `/users/{id}` | PATCH also takes `disabled`, and `username` to rename. Users have `totp_enabled_at` (two-factor login on since) and `totp_required` |
|
||||
| GET / POST | `/users/{id}/tokens` | admin (or yourself) |
|
||||
| GET / POST | `/me/tokens` | `{"name": "laptop", "role": "", "grant_strings": [...], "expires_in": "90d"}`. The response's `token` field is the secret, shown once |
|
||||
| GET | `/tokens` | every token (admin); `?all=1` includes revoked tokens |
|
||||
| DELETE | `/tokens/{id}` | revoke |
|
||||
| POST | `/tokens/{id}/rotate` | New secret, same token. Optional body `{"grace": "1h", "expires_in": "90d", "expires_at": "...", "no_expiry": false}`. The response's `token` field is the new secret, shown once; `previous_secret_valid_until` says when the old one stops. A restricted token may rotate itself and tokens no wider than itself |
|
||||
| POST | `/me/password` | `{"current_password": "...", "password": "..."}` (sessions only) |
|
||||
| GET | `/me/totp` | Your two-factor login: `enabled`, `enabled_at`, `required`, `recovery_codes_left`, `policy` |
|
||||
| POST | `/me/totp/setup` | `{"password": "..."}` (sessions only). A new secret: `secret`, `uri` (`otpauth://`), `qr` (an SVG `data:` URI), `issuer`, `account`. Not active until confirmed. With two-factor login already on (a new phone), it needs a recent code |
|
||||
| POST | `/me/totp/enable` | `{"code": "123456"}`: confirms the secret from setup. Returns `{"recovery_codes": [...]}`, shown once, and logs out your other sessions |
|
||||
| POST | `/me/totp/disable` | `{"password": "...", "code": "..."}` (a code or a recovery code). Refused (409) while `totp.require` applies to you |
|
||||
| POST | `/me/totp/recovery-codes` | New recovery codes, replacing the old ones (needs a recent code) |
|
||||
| DELETE | `/users/{id}/totp` | admin: turn off someone's two-factor login, delete their recovery codes and end their sessions |
|
||||
|
||||
User and token changes take effect immediately. They are not changesets,
|
||||
because they don't touch named, but they are audited.
|
||||
|
||||
Reference in new issue
Block a user