fix(auth): an account with a password deletes itself only with it and a code

A session alone deleted any account, with everything encrypted under its
keys (the second reading's lows; decided by the developer: the password
and a code, as a change of password takes). account.delete now takes a
DeletionProof (the password's SRP proof for a beginChange challenge, a
TOTP or recovery code, the host the device addressed); the register
checks it (requireOwnerToDelete), a member passing it on bound to its own
host. Accounts without a password, anonymous ones among them, delete with
their session as before. In the app, deleting an account with a password
asks for it and a code in a sheet of its own; a wrong one deletes
nothing.

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 21:59:42 +02:00
commit d53844bc93
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -302,6 +302,28 @@ void main() {
expect(anna.password, 'correct horse');
});
test('deletes the account with the password and a code alone, then '
'signed out here; a wrong one or none deletes nothing', () async {
await expectLater(
passwords.deleteAccount(password: 'wrong', totpCode: fakeTotpCode),
failsWith(PasswordProblem.wrongCredentials),
);
await expectLater(
passwords.deleteAccount(password: 'correct horse'),
failsWith(PasswordProblem.wrongCredentials),
);
expect(gateway.accounts, contains('anna@example.org'));
expect(auth.status, isA<SignedIn>());
await passwords.deleteAccount(
password: 'correct horse',
totpCode: fakeTotpCode,
);
expect(gateway.accounts, isNot(contains('anna@example.org')));
expect(auth.status, const SignedOut());
});
test('makes a new recovery phrase, which the instance takes for the '
'password and a code alone', () async {
final phrase = await passwords.newPhrase();