feat(auth): change the password and the recovery phrase while signed in

ADR 0017, step 3. `password.beginChange` and `password.change` take the
old password's SRP proof and a TOTP or recovery code, and the same keys
wrapped under the new password; a session alone changes nothing, and an
account without a confirmed authenticator is told to set one up first.
`password.setRecoveryKeys` replaces the keys' wrapping under the
recovery phrase for a TOTP code, so a device holding the keys can make
a new phrase.

A change of password ends every other session of the account in every
service of the instance; resets of the password and of the account end
them all. The register ends its own and keeps a notice per member
(`sessions_ended_notice`, a migration that only creates it), told at
once and hourly until it confirms, as deletions are told: it names a
moment, so sessions made since stay, and the session that asked for a
change. A member answering one ends its sessions made before that
moment; a member signing someone in after a reset ends its own first
and makes the new session only once its clock is past the moment.

A reset not due says when it takes effect (`takesEffectAt` on
`PasswordRefusedException`), across members too.

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 14:53:58 +02:00
commit d00da1f24a
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 43506 additions and 126 deletions

View file

@ -50,10 +50,15 @@ class PruningFutureCall extends FutureCall {
);
}
/// Tells the instance's members again of the accounts gone that they
/// haven't confirmed yet (ADR 0016), and logs how many confirmed.
/// Tells the instance's members again of the accounts gone (ADR 0016) and
/// of the accounts whose sessions ended (ADR 0017: a password changed or
/// reset) that they haven't confirmed yet, and logs how many confirmed.
Future<void> tellMembersOfDeletions(Session session) async {
final confirmed = await deliverDeletionNotices(session);
session.log('Members confirmed $confirmed deleted accounts.');
final ended = await deliverSessionNotices(session);
session.log(
'Members confirmed $confirmed deleted accounts and $ended accounts '
'whose sessions ended.',
);
}
}

View file

@ -333,8 +333,9 @@ class PruningForgetSignUpAttemptsFutureCall extends _is.FutureCall
}
}
/// Tells the instance's members again of the accounts gone that they
/// haven't confirmed yet (ADR 0016), and logs how many confirmed.
/// Tells the instance's members again of the accounts gone (ADR 0016) and
/// of the accounts whose sessions ended (ADR 0017: a password changed or
/// reset) that they haven't confirmed yet, and logs how many confirmed.
class PruningTellMembersOfDeletionsFutureCall extends _is.FutureCall
implements _is.InvokableFutureCall {
@override