fix(auth): the password alone calls off an account's reset waiting

A sign-in with the password alone, once a reset of the authenticators
took effect, left a reset of the account waiting: whoever read the
owner's mail could ask for one while the owner waited, and carry it out
after the owner was back (the second reading's M2).

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:53:02 +02:00
commit 38bb090575
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1680,6 +1680,53 @@ void main() {
);
});
test('is called off by signing in with the password alone, once a '
"reset of the authenticators took effect: the mailbox's holder "
"can't carry it out after the owner is back", () async {
await annaComplete();
// Anna loses her phone: her password and a code by mail ask.
await atNow(() async {
final challenge = await endpoints.passwordSignIn.begin(
notSignedIn(),
address: 'anna@example.org',
);
final answer = device.answer(challenge);
final code = await mailedCode('anna@example.org');
await endpoints.accountRecovery.resetAuthenticator(
notSignedIn(),
challenge: challenge.challenge,
clientValue: answer.clientValue,
clientProof: answer.clientProof,
address: 'anna@example.org',
language: 'en',
request: code.request,
code: code.code,
);
});
// An hour on, whoever reads her mail asks for the account's reset.
now = now.add(const Duration(hours: 1));
await start();
// Her own reset takes effect: the password alone signs her in.
now = now.add(resetDelay - const Duration(minutes: 59));
expect((await signIn()).needsAuthenticator, isTrue);
// And once more, with nothing left to carry out.
expect((await signIn()).needsAuthenticator, isTrue);
now = now.add(const Duration(hours: 1));
await expectLater(
finish(await PasswordDevice.make(seed: 99)),
refused(PasswordRefusal.noReset),
);
expect(
await AccountReset.db.count(
session,
where: (t) => t.kind.equals(AccountResetKind.account),
),
0,
);
});
test('is refused for a code to another address, and for an address '
'without a password', () async {
await annaSetsPassword();