ReviewOS

also looking at this

mail-os/mail

feat: mailbox recovery addresses and password reset by email

#13
Merged chris wants to merge feat/mailbox-recovery into main
21 files +1503 -10
WEBMAIL.mdmodified+48-0
Changes to WEBMAIL.md
@@ -30,6 +30,46 @@ webmail provisioning, aliases, certificate issuance and renewal live in ts-cloud
3030 Outbox records delivery results. A partial retry contains only failed recipients.
3131 Interrupted dispatch is marked Unknown and requires review before a manual retry.
3232
33## Password recovery
34
35Each mailbox may have one recovery address outside itself. "Forgot password?"
36on the sign-in page (`/forgot`) asks for the mailbox address and always answers
37the same way, whether or not the mailbox exists or has a recovery address. When
38it does, the server mails a reset link to the recovery address, from
39`no-reply@<mailbox domain>` through the normal delivery path, so the message is
40DKIM-signed with that domain's key. The link (`/reset#token=...`) works once,
41expires after 30 minutes, and carries its token in the fragment so it never
42reaches a server or gateway log. Setting the new password signs out every
43session and tells the recovery address that the password was reset.
44
45Limits: at most 3 links per mailbox per hour and one per minute (counted in the
46database, so a restart does not reset them), plus a per-IP limit on requests and
47on token attempts. A link is voided by a newer one, by any password change and
48by any change of the recovery address. Only a token's SHA-256 is stored
49(`mailbox_reset_tokens`).
50
51Links point at `mail.<mailbox domain>` or the server hostname when the request
52came in on one of those names, and at `SMTP_HOSTNAME` otherwise. The request's
53Host header alone is never trusted, because a link built from it would carry the
54token to whichever server the requester named.
55
56Signed-in users see and change their recovery address under Account; saving
57asks for the mailbox password again, and the previous address is notified.
58Operators and deploy provisioning use the CLI:
59
60```bash
61mail user:local recovery pawel@example.com # show
62mail user:local recovery pawel@example.com pawel@elsewhere.example # set (policy managed)
63mail user:local recovery pawel@example.com pawel@elsewhere.example --policy initial
64mail user:local recovery pawel@example.com --clear
65```
66
67`users.recovery_source` records who set the address: `operator` (CLI) or
68`user` (webmail). `--policy initial` sets it unless the owner has set or cleared
69their own; `--policy managed` always sets it. The command prints one
70`Recovery: updated|unchanged|kept|cleared` line and exits non-zero for an
71unknown mailbox or an unusable address.
72
3373## Configuration
3474
3575Declare `infrastructure.compute.managedServices.mail.webmail` in a ts-cloud
@@ -80,6 +120,14 @@ revokes existing webmail sessions.
80120 `delaySeconds` controls the hold. Replaying the same ID cannot dispatch twice.
81121 `GET /webmail/api/outbox` exposes results; `DELETE /webmail/api/outbox/:id`
82122 cancels a pending send. A reviewed Unknown record may be resolved explicitly.
123- `POST /webmail/auth/forgot` `{address}` always returns `{ok:true}` (429 when
124 the IP is over its limit). `POST /webmail/auth/reset/verify` `{token}` returns
125 `{account}` without spending the token; `POST /webmail/auth/reset`
126 `{token,password}` spends it and sets the password. A dead token is 400
127 `invalid_token`. These three need no session but must be same-origin.
128- `GET /webmail/auth/recovery` returns `{recovery, setBy}`;
129 `PUT /webmail/auth/recovery` `{currentPassword, recovery}` changes it, and an
130 empty `recovery` removes it.
83131- Existing immediate-compose and message endpoints remain compatible with the
84132 native SDK and older clients. New clients use the durable draft and queue APIs.
85133