fix(auth): a password set up for good stays, through resets too
`password.set` replaced a password whenever the account had no confirmed authenticator, which is also the state an authenticator's or the account's reset leaves behind; and in that state a code by mail or GitHub signed in again. So whoever got a session another way could replace the password and the keys, skipping the reset's 72 hours. An account had a confirmed authenticator while it has one or a reset of it stands (`hadConfirmedAuthenticator`: the reset's row stays until a new authenticator is confirmed). Then `password.set` is refused (`passwordExists`), and the account signs in with its password alone: codes by mail and GitHub are refused (`usePassword`), the latter through `SignInWays.passwordOnly`, which members read from the register. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
892fba97e8
commit
4e425cb17f
2 changed files with 167 additions and 19 deletions
|
|
@ -1248,6 +1248,67 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('once carried out, leaves the password to no session alone, and '
|
||||
'the account to its password, until one is confirmed '
|
||||
'again', () async {
|
||||
await annaComplete();
|
||||
await GitHubAccount.db.insertRow(
|
||||
session,
|
||||
GitHubAccount(userIdentifier: '1001', authUserId: anna),
|
||||
);
|
||||
await resetByMail();
|
||||
now = now.add(resetDelay);
|
||||
expect((await signIn()).needsAuthenticator, isTrue);
|
||||
expect(await TotpAuthenticator.db.count(session), 0);
|
||||
|
||||
final other = await PasswordDevice.make(seed: 99);
|
||||
await expectLater(
|
||||
endpoints.password.set(asAnna(), setup: other.setup()),
|
||||
refused(PasswordRefusal.passwordExists),
|
||||
);
|
||||
expect(
|
||||
Uint8List.sublistView(
|
||||
(await AccountPassword.db.findFirstRow(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(anna),
|
||||
))!.verifier,
|
||||
),
|
||||
Uint8List.sublistView(device.verifier),
|
||||
);
|
||||
// Nor does a code by mail or GitHub sign in in its place.
|
||||
expect(await signsInWithPassword(session, anna), isTrue);
|
||||
final ben = await newAnonymousPerson(session);
|
||||
final asBen = sessionBuilder.copyWith(
|
||||
authentication: AuthenticationOverride.authenticationInfo(
|
||||
ben.uuid,
|
||||
{},
|
||||
),
|
||||
);
|
||||
final request = await endpoints.emailCode.sendCode(
|
||||
asBen,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
await expectLater(
|
||||
endpoints.emailCode.verifyCode(
|
||||
asBen,
|
||||
request: request,
|
||||
code: mailbox.codeFor('anna@example.org'),
|
||||
),
|
||||
refusedAsEmail(EmailSignInRefusal.usePassword),
|
||||
);
|
||||
await expectLater(
|
||||
gitHubAccountToSignIn(session, '1001'),
|
||||
throwsA(
|
||||
isA<GitHubSignInRefusedException>().having(
|
||||
(error) => error.reason,
|
||||
'reason',
|
||||
GitHubSignInRefusal.usePassword,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('is called off by signing in with a code meanwhile', () async {
|
||||
final complete = await annaComplete();
|
||||
await resetByMail();
|
||||
|
|
@ -1659,6 +1720,21 @@ void main() {
|
|||
fresh.signingPublicKey,
|
||||
);
|
||||
await expectLater(finish(fresh), refused(PasswordRefusal.noReset));
|
||||
// A session alone replaces neither, before an authenticator is
|
||||
// confirmed again.
|
||||
await expectLater(
|
||||
endpoints.password.set(asAnna(), setup: device.setup()),
|
||||
refused(PasswordRefusal.passwordExists),
|
||||
);
|
||||
expect(
|
||||
Uint8List.sublistView(
|
||||
(await AccountPassword.db.findFirstRow(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(anna),
|
||||
))!.verifier,
|
||||
),
|
||||
Uint8List.sublistView(fresh.verifier),
|
||||
);
|
||||
});
|
||||
|
||||
test('is called off by signing in with the password', () async {
|
||||
|
|
|
|||
|
|
@ -219,6 +219,8 @@ final class _Register implements AccountRegister {
|
|||
email: addresses.containsValue(person),
|
||||
password: passwords.contains(person),
|
||||
authenticator: authenticated.contains(person),
|
||||
passwordOnly:
|
||||
passwords.contains(person) && authenticated.contains(person),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue