fix(auth): a reset waits while its owner is held out, and devices show it

The full reading's H1: an owner could be kept from calling a reset off, and
the reset then went through. Whoever knew the password and had the mailbox
asked an authenticators' reset and kept the codes waiting, so the owner's
sign-ins were refused until it took effect; whoever had the mailbox filled
the address's proofs ceiling from three networks and completed an account
reset. Now every hold-out of the owner after a reset was asked (a wait
that begins, an attempt a wait refuses, a sign-in or proof refused by the
address's ceiling) moves the account's resets not carried out to take
effect no sooner than 72 hours after it ends (pushOutResets). A reset
carried out (the password alone signing in until an authenticator is
confirmed) is never pushed, nor a lapsed one brought back.

Signed-in devices are told too: SignInWays carries when a waiting reset
of each kind takes effect, the account section shows them with Call it
off, which takes the owner's proof (password and code; password.
callOffResets, forwarded by members) and calls every reset off, as no wait
holds a signed-in owner's proof back. Texts in English and German.

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:17:30 +02:00
commit 9d0d037c86
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -453,6 +453,13 @@ final class _Register implements AccountRegister {
changedWith.add(proof);
});
@override
Future<void> callOffResets(
Session session, {
required UuidValue person,
required OwnerProof proof,
}) => _answer('callOffResets', () {});
@override
Future<void> setRecoveryKeys(
Session session, {
@ -1285,6 +1292,37 @@ void main() {
expect(register.warned, isEmpty);
});
test("passes its person's calling off of resets to the register, "
'never one signed out', () async {
await endpoints.password.callOffResets(
asBen(),
proof: OwnerProof(
challenge: const Uuid().v7obj(),
clientValue: ByteData(384),
clientProof: ByteData(32),
totpCode: '123456',
host: testHost,
),
);
expect(register.asked, contains('callOffResets'));
register.asked.clear();
await expectLater(
endpoints.password.callOffResets(
sessionBuilder.copyWith(
authentication: AuthenticationOverride.unauthenticated(),
),
proof: OwnerProof(
challenge: const Uuid().v7obj(),
clientValue: ByteData(384),
clientProof: ByteData(32),
host: testHost,
),
),
throwsA(isA<ServerpodUnauthenticatedException>()),
);
expect(register.asked, isNot(contains('callOffResets')));
});
test("passes its person's dismissing of the warning to the register, "
"never another's", () async {
await endpoints.password.dismissSessionEndedWarning(asBen());