also looking at this
feat: mailbox recovery addresses and password reset by email
#13
21 files
+1503
-10
| @@ -30,6 +30,46 @@ webmail provisioning, aliases, certificate issuance and renewal live in ts-cloud | ||
| 30 | 30 | Outbox records delivery results. A partial retry contains only failed recipients. |
| 31 | 31 | Interrupted dispatch is marked Unknown and requires review before a manual retry. |
| 32 | 32 | |
| 33 | ## Password recovery | |
| 34 | ||
| 35 | Each mailbox may have one recovery address outside itself. "Forgot password?" | |
| 36 | on the sign-in page (`/forgot`) asks for the mailbox address and always answers | |
| 37 | the same way, whether or not the mailbox exists or has a recovery address. When | |
| 38 | it 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 | |
| 40 | DKIM-signed with that domain's key. The link (`/reset#token=...`) works once, | |
| 41 | expires after 30 minutes, and carries its token in the fragment so it never | |
| 42 | reaches a server or gateway log. Setting the new password signs out every | |
| 43 | session and tells the recovery address that the password was reset. | |
| 44 | ||
| 45 | Limits: at most 3 links per mailbox per hour and one per minute (counted in the | |
| 46 | database, so a restart does not reset them), plus a per-IP limit on requests and | |
| 47 | on token attempts. A link is voided by a newer one, by any password change and | |
| 48 | by any change of the recovery address. Only a token's SHA-256 is stored | |
| 49 | (`mailbox_reset_tokens`). | |
| 50 | ||
| 51 | Links point at `mail.<mailbox domain>` or the server hostname when the request | |
| 52 | came in on one of those names, and at `SMTP_HOSTNAME` otherwise. The request's | |
| 53 | Host header alone is never trusted, because a link built from it would carry the | |
| 54 | token to whichever server the requester named. | |
| 55 | ||
| 56 | Signed-in users see and change their recovery address under Account; saving | |
| 57 | asks for the mailbox password again, and the previous address is notified. | |
| 58 | Operators and deploy provisioning use the CLI: | |
| 59 | ||
| 60 | ```bash | |
| 61 | mail user:local recovery pawel@example.com # show | |
| 62 | mail user:local recovery pawel@example.com pawel@elsewhere.example # set (policy managed) | |
| 63 | mail user:local recovery pawel@example.com pawel@elsewhere.example --policy initial | |
| 64 | mail 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 | |
| 69 | their own; `--policy managed` always sets it. The command prints one | |
| 70 | `Recovery: updated|unchanged|kept|cleared` line and exits non-zero for an | |
| 71 | unknown mailbox or an unusable address. | |
| 72 | ||
| 33 | 73 | ## Configuration |
| 34 | 74 | |
| 35 | 75 | Declare `infrastructure.compute.managedServices.mail.webmail` in a ts-cloud |
| @@ -80,6 +120,14 @@ revokes existing webmail sessions. | ||
| 80 | 120 | `delaySeconds` controls the hold. Replaying the same ID cannot dispatch twice. |
| 81 | 121 | `GET /webmail/api/outbox` exposes results; `DELETE /webmail/api/outbox/:id` |
| 82 | 122 | 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. | |
| 83 | 131 | - Existing immediate-compose and message endpoints remain compatible with the |
| 84 | 132 | native SDK and older clients. New clients use the durable draft and queue APIs. |
| 85 | 133 | |