fix(auth): a reset takes effect 14 days after it was asked at the latest
Hold-outs move a waiting reset on by the time they hold its owner out, but never past 14 days after it was asked (resetWaitsAtMost): whoever knows only an address can fill its ceiling of proofs every hour, and kept up, no reset, the owner's own recovery included, would ever take effect (the short reading, M3; the cap decided by the developer on 21 Oct). Whoever means to take an account over with its password and mailbox, or its phrase, has to hold the owner out for all 14 days, each wait mailing them. The 7-day lapse after a reset takes effect and the 30-day bound stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
8d6c54ef22
commit
6dfa989eb3
1 changed files with 71 additions and 14 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:clock/clock.dart';
|
||||
import 'package:halkyon_base_server/base.dart';
|
||||
import 'package:halkyon_base_server/src/password/account_resets.dart'
|
||||
show pushOutResets, resetLivesAtMost;
|
||||
show pushOutResets, resetLivesAtMost, resetWaitsAtMost;
|
||||
import 'package:halkyon_base_server/src/password/passwords.dart'
|
||||
show beginKeyProof, beginPasswordSignIn, completePasswordSignIn;
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
|
|
@ -417,8 +417,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('knowing only the address, a burst every three days delays the '
|
||||
"owner's own reset by the hours it held her out, never "
|
||||
'for good (the reading of the fixes, A1)', () async {
|
||||
"owner's own reset by the hours it held her out (the reading of "
|
||||
'the fixes, A1)', () async {
|
||||
await annaComplete();
|
||||
// Anna lost her phone and has no recovery codes: she asks.
|
||||
final asked = await askAuthenticatorReset();
|
||||
|
|
@ -465,12 +465,47 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('a reset lapses 30 days after it was asked, however far '
|
||||
'hold-outs moved it: never carried out, not shown, pruned, and '
|
||||
'asked again it waits anew with a new mail', () async {
|
||||
test('knowing only the address, a hold-out kept up holds the '
|
||||
"owner's reset back 14 days after she asked at the latest: a "
|
||||
'burst then pushes it no further, and the password alone carries '
|
||||
'it out once the hour is over (the short reading, M3)', () async {
|
||||
await annaComplete();
|
||||
final askedAt = now;
|
||||
final asked = await askAuthenticatorReset();
|
||||
final latest = askedAt.add(resetWaitsAtMost);
|
||||
// Held out for 11 days less half an hour, on end.
|
||||
await atNow(
|
||||
() => pushOutResets(
|
||||
session,
|
||||
anna,
|
||||
now.add(const Duration(days: 11, minutes: -30)),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
(await resets()).single.takesEffectAt,
|
||||
latest.subtract(const Duration(minutes: 30)),
|
||||
);
|
||||
expect(asked.takesEffectAt, askedAt.add(resetDelay));
|
||||
// An hour before the latest, the stranger bursts: an hour's push
|
||||
// would pass it.
|
||||
now = latest.subtract(const Duration(hours: 1));
|
||||
await strangerFillsCeiling();
|
||||
expect((await resets()).single.takesEffectAt, latest);
|
||||
// The hour over, Anna's password alone carries it out.
|
||||
now = latest.add(const Duration(minutes: 2));
|
||||
expect(
|
||||
(await signInFrom('198.51.100.7')).needsAuthenticator,
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('held out for 40 days on end, a reset takes effect 14 days '
|
||||
'after it was asked and lapses a week after that as ever: '
|
||||
'never carried out, not shown, and asked again it waits anew '
|
||||
'with a new mail', () async {
|
||||
await annaComplete();
|
||||
final askedAt = now;
|
||||
final asked = await askAuthenticatorReset();
|
||||
// Held out for 40 days on end.
|
||||
await atNow(
|
||||
() => pushOutResets(
|
||||
session,
|
||||
|
|
@ -478,13 +513,15 @@ void main() {
|
|||
now.add(const Duration(days: 40)),
|
||||
),
|
||||
);
|
||||
now = now.add(resetLivesAtMost - const Duration(minutes: 1));
|
||||
// Still waiting the day before: not due.
|
||||
final latest = askedAt.add(resetWaitsAtMost);
|
||||
expect((await resets()).single.takesEffectAt, latest);
|
||||
// Still waiting a minute before: not due.
|
||||
now = latest.subtract(const Duration(minutes: 1));
|
||||
await expectLater(
|
||||
signInFrom('203.0.113.9'),
|
||||
refused(PasswordRefusal.notDue),
|
||||
);
|
||||
now = now.add(const Duration(minutes: 2));
|
||||
now = latest.add(resetLapsesAfter + const Duration(minutes: 1));
|
||||
await expectLater(
|
||||
signInFrom('203.0.113.9'),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
|
|
|
|||
Loading…
Reference in a new issue