fix(auth): a new recovery phrase takes the password too

password.setRecoveryKeys took a session and a TOTP code alone, so a phone
signed in with its authenticator beside it could set a phrase of its
holder's own and, through the phrase's reset, a new password, never
knowing the old one. It now takes the old password's SRP proof (from
beginChange, the caller's own) and a TOTP or recovery code, as changing
the password does: one owner proof for both (_proveOwner), forwarded by
members, asked for in the account section's New recovery phrase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-20 20:01:07 +02:00
commit 5fb28bd817
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -15,7 +15,7 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
| Endpoint | Methods | Who may call |
|---|---|---|
| `anonymousIdp` | `login` (Serverpod's anonymous sign-in, at most 30 new accounts per network and hour) | anyone |
| `password` | `set`: the caller's password, with the address a mailed code proves unless they have one; `beginChange`, `change`: a new password with the old one's proof and a code, every other session ending; `setRecoveryKeys`: a new recovery phrase's wrapping, for a code | signed in |
| `password` | `set`: the caller's password, with the address a mailed code proves unless they have one; `beginChange`, `change`: a new password with the old one's proof and a code, every other session ending; `setRecoveryKeys`: a new recovery phrase's wrapping, with the old password's proof and a code | signed in |
| `passwordSignIn` | `begin`, `complete`: SRP-6a's two steps with a code from an authenticator (or a recovery code; a setup never finished, a code by mail); a session. `signUp`: an account at the address a mailed code proves, with a password; a session | without a session |
| `authenticator` | `enrol`, `confirm` (the first brings ten recovery codes; another takes a code from one confirmed before), `list`, `remove`, `newRecoveryCodes` | signed in |
| `accountRecovery` | `resetAuthenticator`, `beginKeyProof`, `resetPassword` (with the recovery phrase), `startAccountReset`, `completeAccountReset` | without a session |
@ -216,8 +216,9 @@ Built on the three parts above, on the register (members forward every step thro
password, new keys, no authenticators.
- **Changing while signed in:** `password.beginChange` and `password.change` take the old
password's SRP proof, a TOTP or recovery code and the same keys wrapped under the new
password; `password.setRecoveryKeys` replaces the phrase's wrapping for a code. A
session alone changes neither.
password; `password.setRecoveryKeys` replaces the phrase's wrapping with the same
proof and code (a phrase of one's own would otherwise set a new password through the
phrase's reset). A session alone changes neither, nor a session and a code.
- **Sessions end everywhere:** a change of password ends every other session of the
account; a reset of the password or the account ends all. The register ends its own
and keeps a `sessions_ended_notice` per member (told at once and hourly until it