fix(auth): a member asks the register before deleting an account

The full reading's L7: a member deleted at the register only for a session
with the identified scope, so a person the register had linked whose
becoming identified on the member failed was deleted on the member on the
session alone, their account left at the register. Now the member asks the
register whether the person is its account (knownAccounts) and, if so,
deletes there with the proof it takes; else, here alone. While the
register is out of reach, nothing is deleted.

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-21 13:35:42 +02:00
commit 0119aacbc7
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 24 additions and 7 deletions

View file

@ -1,6 +1,5 @@
import 'package:halkyon_base_server/src/auth/account.dart';
import 'package:halkyon_base_server/src/auth/membership.dart';
import 'package:halkyon_base_server/src/auth/scopes.dart';
import 'package:halkyon_base_server/src/generated/protocol.dart';
import 'package:halkyon_base_server/src/password/key_proof.dart';
import 'package:halkyon_base_server/src/password/owner_proof_tries.dart';
@ -32,10 +31,15 @@ class AccountEndpoint extends Endpoint {
await asOwnerProofTry(session, bound, () async {
if (!isMember(pod)) {
await requireOwnerToDelete(session, person, bound);
} else if (session.authenticated!.scopes.contains(identified)) {
await AccountRegister.of(
pod,
).deleteAccount(session, person, proof: bound);
return;
}
// An account of the register's whatever this session's scopes say
// (the full reading's L7: a person the register linked whose
// becoming identified here failed was deleted here on the session
// alone): the register decides, with the proof it takes.
final register = AccountRegister.of(pod);
if ((await register.knownAccounts(session, {person})).contains(person)) {
await register.deleteAccount(session, person, proof: bound);
}
});
await deletePerson(session, person);

View file

@ -1022,14 +1022,27 @@ void main() {
expect(register.deletedWith.single?.totpCode, '123456');
});
test('of an anonymous person deletes it here alone', () async {
test('of an anonymous person deletes it here alone, once the '
'register says it has no such account', () async {
await endpoints.account.delete(asAnna());
expect(register.asked, isEmpty);
expect(register.asked, ['knownAccounts']);
expect(await AuthUser.db.findById(session, anna), isNull);
expect(hostDeleted, [anna]);
});
test(
'of a person the register knows deletes it at the register, '
"whatever this session's scopes say (the full reading's L7)",
() async {
// Linked at the register, her becoming identified here failed.
register.accounts.add(anna);
await endpoints.account.delete(asAnna());
expect(register.deleted, [anna]);
},
);
test('is refused while the register is out of reach, deleting '
'nothing here', () async {
register.failing = RegisterUnavailableException('test');