fix(auth): limits by address and network, so nobody holds an owner out
Anyone who knew an address could spend its whole hour: 10 sign-ins, 5 phrase proofs and 3 codes, each new code voiding the last (the second reading's M3). Now: - sign-ins begun count per address and network (10 an hour), phrase proofs likewise (5), and networks as before (30); - failed proofs count per address from every network: past 30 an hour (wrong passwords, phrases' signatures by another key, an address nobody has alike) no sign-in or proof begins for it anywhere, so guessing from many networks makes at most 30 guesses an hour, while holding an owner out takes failing without end from at least three; longer windows would outlive the hourly pruning of the limits' notes; - a signed-in change of password begins 10 times an hour per account; - codes by mail count per address and network (3 an hour), per address from every network (10, no mailbox flooded) and per network (10), and up to 3 are valid at once, a fourth voiding the oldest, so codes others ask for don't void the one its owner is typing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
5b9cb85b0f
commit
26488b40ce
1 changed files with 300 additions and 131 deletions
|
|
@ -244,8 +244,9 @@ void main() {
|
|||
expect(await _addressesOf(session, anna), isEmpty);
|
||||
});
|
||||
|
||||
test('a code past its 15 minutes, a code used, and one replaced by '
|
||||
'a newer code', () async {
|
||||
test('a code past its 15 minutes, a code used, and the oldest of '
|
||||
'four: up to three are valid at once, so codes others ask for '
|
||||
"don't void the one the owner is typing", () async {
|
||||
final late = await endpoints.emailCode.sendCode(
|
||||
asAnna(),
|
||||
address: 'anna@example.org',
|
||||
|
|
@ -264,75 +265,71 @@ void main() {
|
|||
),
|
||||
);
|
||||
|
||||
final replaced = await endpoints.emailCode.sendCode(
|
||||
asAnna(),
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
final replacedCode = mailbox.codeFor('anna@example.org');
|
||||
final newer = await endpoints.emailCode.sendCode(
|
||||
asAnna(),
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
await expectLater(
|
||||
proveAddressCode(
|
||||
Future<(UuidValue, String)> ask(String network) async {
|
||||
final request = await sendSignInCode(
|
||||
session,
|
||||
request: replaced,
|
||||
code: replacedCode,
|
||||
),
|
||||
throwsA(_refusal(EmailSignInRefusal.codeExpired)),
|
||||
);
|
||||
|
||||
final newerCode = mailbox.codeFor('anna@example.org');
|
||||
await proveAddressCode(
|
||||
session,
|
||||
request: newer,
|
||||
code: newerCode,
|
||||
);
|
||||
await expectLater(
|
||||
proveAddressCode(
|
||||
session,
|
||||
request: newer,
|
||||
code: newerCode,
|
||||
),
|
||||
throwsA(_refusal(EmailSignInRefusal.codeExpired)),
|
||||
);
|
||||
});
|
||||
|
||||
test('a fourth code for one address within an hour, and an '
|
||||
'eleventh from one network', () async {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
await endpoints.emailCode.sendCode(
|
||||
asAnna(),
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
network: network,
|
||||
);
|
||||
return (request, mailbox.codeFor('anna@example.org'));
|
||||
}
|
||||
|
||||
final (owners, ownersCode) = await ask('203.0.113.1');
|
||||
final (second, secondCode) = await ask('198.51.100.7');
|
||||
final (third, thirdCode) = await ask('198.51.100.7');
|
||||
// Three valid: the owner's still is.
|
||||
await proveAddressCode(session, request: owners, code: ownersCode);
|
||||
final (fourth, fourthCode) = await ask('198.51.100.7');
|
||||
final (fifth, _) = await ask('192.0.2.9');
|
||||
// Five asked for, three valid: the two oldest left went.
|
||||
await expectLater(
|
||||
proveAddressCode(session, request: second, code: secondCode),
|
||||
throwsA(_refusal(EmailSignInRefusal.codeExpired)),
|
||||
);
|
||||
await proveAddressCode(session, request: third, code: thirdCode);
|
||||
await proveAddressCode(session, request: fourth, code: fourthCode);
|
||||
await expectLater(
|
||||
proveAddressCode(session, request: fourth, code: fourthCode),
|
||||
throwsA(_refusal(EmailSignInRefusal.codeExpired)),
|
||||
);
|
||||
expect(fifth, isNotNull);
|
||||
});
|
||||
|
||||
test('a fourth code for one address from one network within an '
|
||||
'hour, an eleventh for one address from any, and an eleventh '
|
||||
'from one network', () async {
|
||||
Future<void> ask(String address, String network) => sendSignInCode(
|
||||
session,
|
||||
address: address,
|
||||
language: 'en',
|
||||
network: network,
|
||||
);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
await ask('anna@example.org', '203.0.113.1');
|
||||
}
|
||||
await expectLater(
|
||||
endpoints.emailCode.sendCode(
|
||||
asAnna(),
|
||||
address: 'Anna@Example.org',
|
||||
language: 'en',
|
||||
),
|
||||
ask('Anna@Example.org', '203.0.113.1'),
|
||||
throwsA(_refusal(EmailSignInRefusal.tooManyCodes)),
|
||||
);
|
||||
// Another network still gets one: whoever filled the first count
|
||||
// holds back no other, the owner's among them.
|
||||
for (var i = 0; i < 7; i++) {
|
||||
await endpoints.emailCode.sendCode(
|
||||
asAnna(),
|
||||
address: 'anna$i@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
await ask('anna@example.org', '198.51.100.${i ~/ 3}');
|
||||
}
|
||||
// Ten mailed to one address in the hour: no more, from anywhere.
|
||||
await expectLater(
|
||||
endpoints.emailCode.sendCode(
|
||||
asAnna(),
|
||||
address: 'anna.else@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
ask('anna@example.org', '192.0.2.200'),
|
||||
throwsA(_refusal(EmailSignInRefusal.tooManyCodes)),
|
||||
);
|
||||
expect(mailbox.sent, hasLength(10));
|
||||
for (var i = 0; i < 10; i++) {
|
||||
await ask('anna$i@example.org', '192.0.2.9');
|
||||
}
|
||||
await expectLater(
|
||||
ask('anna.else@example.org', '192.0.2.9'),
|
||||
throwsA(_refusal(EmailSignInRefusal.tooManyCodes)),
|
||||
);
|
||||
expect(mailbox.sent, hasLength(20));
|
||||
});
|
||||
|
||||
test('saying how someone signs in when not signed in', () async {
|
||||
|
|
|
|||
Loading…
Reference in a new issue