fix(auth): a proof the register couldn't answer counts; the sign-up mail says it gives a password
Two of the fourth reading's lows:
- A signed-in proof whose call to the register failed
(RegisterUnavailableException, a member's call timing out after the
register checked it) stays counted against the session, rather than
taken back: a guess never goes uncounted.
- A sign-up code also gives an existing account with that address its
first password, which its mail now says in the subject too ("create an
account or give it a password").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
d7855162c7
commit
f154b7ea7c
4 changed files with 50 additions and 10 deletions
|
|
@ -97,7 +97,7 @@ final class LogCodeMailer implements CodeMailer {
|
||||||
// A compound with the name, hyphenated through: Halkyon-Learn-Code.
|
// A compound with the name, hyphenated through: Halkyon-Learn-Code.
|
||||||
subject:
|
subject:
|
||||||
'$code: dein ${app.replaceAll(' ', '-')}-Code, ${switch (purpose) {
|
'$code: dein ${app.replaceAll(' ', '-')}-Code, ${switch (purpose) {
|
||||||
EmailCodePurpose.signUp => 'um ein Konto anzulegen',
|
EmailCodePurpose.signUp => 'um ein Konto anzulegen oder ihm ein Passwort zu geben',
|
||||||
EmailCodePurpose.finishSetup => 'um dein Konto fertig einzurichten',
|
EmailCodePurpose.finishSetup => 'um dein Konto fertig einzurichten',
|
||||||
EmailCodePurpose.authenticatorReset => 'um deine Authenticator-App abzuschalten',
|
EmailCodePurpose.authenticatorReset => 'um deine Authenticator-App abzuschalten',
|
||||||
EmailCodePurpose.accountReset => 'um dein Konto zurückzusetzen',
|
EmailCodePurpose.accountReset => 'um dein Konto zurückzusetzen',
|
||||||
|
|
@ -133,7 +133,7 @@ final class LogCodeMailer implements CodeMailer {
|
||||||
_ => (
|
_ => (
|
||||||
subject:
|
subject:
|
||||||
'$code: your $app code to ${switch (purpose) {
|
'$code: your $app code to ${switch (purpose) {
|
||||||
EmailCodePurpose.signUp => 'create an account',
|
EmailCodePurpose.signUp => 'create an account or give it a password',
|
||||||
EmailCodePurpose.finishSetup => 'finish setting up your account',
|
EmailCodePurpose.finishSetup => 'finish setting up your account',
|
||||||
EmailCodePurpose.authenticatorReset => 'switch off your authenticator',
|
EmailCodePurpose.authenticatorReset => 'switch off your authenticator',
|
||||||
EmailCodePurpose.accountReset => 'reset your account',
|
EmailCodePurpose.accountReset => 'reset your account',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:halkyon_base_server/src/generated/protocol.dart';
|
import 'package:halkyon_base_server/src/generated/protocol.dart';
|
||||||
|
import 'package:halkyon_base_server/src/register/register_protocol.dart';
|
||||||
import 'package:serverpod/serverpod.dart';
|
import 'package:serverpod/serverpod.dart';
|
||||||
import 'package:serverpod_auth_idp_server/core.dart';
|
import 'package:serverpod_auth_idp_server/core.dart';
|
||||||
|
|
||||||
|
|
@ -24,7 +25,8 @@ const ownerProofTriesBeforeSignOut = 10;
|
||||||
|
|
||||||
/// The refusal that says a proof (the password, or the code with it) was
|
/// The refusal that says a proof (the password, or the code with it) was
|
||||||
/// wrong: it counts. Any other outcome (a refusal of something else, an
|
/// wrong: it counts. Any other outcome (a refusal of something else, an
|
||||||
/// error) checked no proof or found it right, and is taken back.
|
/// error) checked no proof or found it right, and is taken back, but for a
|
||||||
|
/// register out of reach, which may have checked it.
|
||||||
const _wrongProofs = {PasswordRefusal.wrongCredentials};
|
const _wrongProofs = {PasswordRefusal.wrongCredentials};
|
||||||
|
|
||||||
/// [check]'s outcome, with [proof] sent from [session]'s signed-in session
|
/// [check]'s outcome, with [proof] sent from [session]'s signed-in session
|
||||||
|
|
@ -78,6 +80,10 @@ Future<T> asOwnerProofTry<T>(
|
||||||
throw PasswordRefusedException(reason: PasswordRefusal.sessionEnded);
|
throw PasswordRefusedException(reason: PasswordRefusal.sessionEnded);
|
||||||
}
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
|
} on RegisterUnavailableException {
|
||||||
|
// The register may have checked it before the answer was lost (a
|
||||||
|
// member's call timing out): it stays counted (the fourth reading).
|
||||||
|
rethrow;
|
||||||
} on Object {
|
} on Object {
|
||||||
await _takeBack(session, sessionId);
|
await _takeBack(session, sessionId);
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
|
||||||
|
|
@ -727,6 +727,33 @@ void main() {
|
||||||
expect(left.single.createdAt.isBefore(endedBefore), isFalse);
|
expect(left.single.createdAt.isBefore(endedBefore), isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("count a proof whose answer the register couldn't give, as it "
|
||||||
|
'may have checked it (the fourth reading)', () async {
|
||||||
|
register.failing = RegisterUnavailableException('test');
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
endpoints.authenticator.newRecoveryCodes(
|
||||||
|
asAnna(),
|
||||||
|
proof: OwnerProof(
|
||||||
|
challenge: const Uuid().v7obj(),
|
||||||
|
clientValue: ByteData(384),
|
||||||
|
clientProof: ByteData(32),
|
||||||
|
host: testHost,
|
||||||
|
totpCode: '123456',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
throwsA(isA<RegisterUnavailableException>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(await SessionCodeFailures.db.findFirstRow(
|
||||||
|
session,
|
||||||
|
where: (t) => t.sessionId.equals(annasPhone),
|
||||||
|
))?.failures,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('sign up at the register, the new account signed in here, '
|
test('sign up at the register, the new account signed in here, '
|
||||||
'identified', () async {
|
'identified', () async {
|
||||||
final device = await PasswordDevice.make();
|
final device = await PasswordDevice.make();
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,8 @@ void main() {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
mail.subject,
|
mail.subject,
|
||||||
'K7QX-M2PD: your Halkyon Learn code to create an account',
|
'K7QX-M2PD: your Halkyon Learn code to create an account or give it '
|
||||||
|
'a password',
|
||||||
);
|
);
|
||||||
expect(mail.body, startsWith('K7QX-M2PD\n\n'));
|
expect(mail.body, startsWith('K7QX-M2PD\n\n'));
|
||||||
expect(mail.body, contains('create your account on learn.halkyon.eu'));
|
expect(mail.body, contains('create your account on learn.halkyon.eu'));
|
||||||
|
|
@ -173,7 +174,8 @@ void main() {
|
||||||
).subject;
|
).subject;
|
||||||
const english = {
|
const english = {
|
||||||
EmailCodePurpose.signUp:
|
EmailCodePurpose.signUp:
|
||||||
'K7QX-M2PD: your Halkyon Learn code to create an account',
|
'K7QX-M2PD: your Halkyon Learn code to create an account or give '
|
||||||
|
'it a password',
|
||||||
EmailCodePurpose.finishSetup:
|
EmailCodePurpose.finishSetup:
|
||||||
'K7QX-M2PD: your Halkyon Learn code to finish setting up your '
|
'K7QX-M2PD: your Halkyon Learn code to finish setting up your '
|
||||||
'account',
|
'account',
|
||||||
|
|
@ -190,7 +192,8 @@ void main() {
|
||||||
};
|
};
|
||||||
const german = {
|
const german = {
|
||||||
EmailCodePurpose.signUp:
|
EmailCodePurpose.signUp:
|
||||||
'K7QX-M2PD: dein Halkyon-Learn-Code, um ein Konto anzulegen',
|
'K7QX-M2PD: dein Halkyon-Learn-Code, um ein Konto anzulegen oder '
|
||||||
|
'ihm ein Passwort zu geben',
|
||||||
EmailCodePurpose.finishSetup:
|
EmailCodePurpose.finishSetup:
|
||||||
'K7QX-M2PD: dein Halkyon-Learn-Code, um dein Konto fertig '
|
'K7QX-M2PD: dein Halkyon-Learn-Code, um dein Konto fertig '
|
||||||
'einzurichten',
|
'einzurichten',
|
||||||
|
|
@ -248,11 +251,13 @@ void main() {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
mailIn('de').subject,
|
mailIn('de').subject,
|
||||||
'K7QX-M2PD: dein Halkyon-Learn-Code, um ein Konto anzulegen',
|
'K7QX-M2PD: dein Halkyon-Learn-Code, um ein Konto anzulegen oder ihm '
|
||||||
|
'ein Passwort zu geben',
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
mailIn('fr').subject,
|
mailIn('fr').subject,
|
||||||
'K7QX-M2PD: your Halkyon Learn code to create an account',
|
'K7QX-M2PD: your Halkyon Learn code to create an account or give '
|
||||||
|
'it a password',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -267,12 +272,14 @@ void main() {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
mailIn('en').subject,
|
mailIn('en').subject,
|
||||||
'K7QX-M2PD: your Halkyon Todo code to create an account',
|
'K7QX-M2PD: your Halkyon Todo code to create an account or give it a '
|
||||||
|
'password',
|
||||||
);
|
);
|
||||||
expect(mailIn('en').body, contains('Type this code in Halkyon Todo'));
|
expect(mailIn('en').body, contains('Type this code in Halkyon Todo'));
|
||||||
expect(
|
expect(
|
||||||
mailIn('de').subject,
|
mailIn('de').subject,
|
||||||
'K7QX-M2PD: dein Halkyon-Todo-Code, um ein Konto anzulegen',
|
'K7QX-M2PD: dein Halkyon-Todo-Code, um ein Konto anzulegen oder '
|
||||||
|
'ihm ein Passwort zu geben',
|
||||||
);
|
);
|
||||||
expect(mailIn('de').body, contains('Gib diesen Code in Halkyon Todo'));
|
expect(mailIn('de').body, contains('Gib diesen Code in Halkyon Todo'));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue