fix(auth)!: the first authenticator takes the password, and a reset ends every session
The fourth reading's second finding: a stolen session outlived the reset of the authenticators (carrying it out ended no session) and confirmed the account's first authenticator on its own code alone, ahead of the owner, so the owner's confirmation and sign-in were refused after. - Carrying out a reset of the authenticators ends every session of the account, on the register at once and on every member by notice, as the other resets do; the sign-in that carries it out gets its session after that moment (PasswordProof.sessionsEndedBefore, issueSessionAfter). - Confirming the first authenticator takes the password's proof for a change's challenge, without a code (proveOwnersPassword), counted per session as the owner's proofs are: a session of a lost phone, or of a setup never finished, claims no second factor. The app sends the password with every confirmation: the sign-up's, a reset's and the account section's (which asks for it), and a sign-in's own unlock where the session is still held back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
f0413531ff
commit
d7855162c7
1 changed files with 548 additions and 95 deletions
|
|
@ -683,6 +683,50 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('carrying out a reset of the authenticators, end the sessions '
|
||||
'here made before the register ended them, the new one staying '
|
||||
'(the fourth reading)', () async {
|
||||
final endedBefore = DateTime.now().toUtc();
|
||||
register.proof = PasswordProof(
|
||||
account: anna,
|
||||
serverProof: ByteData(32),
|
||||
encryptionPublicKey: ByteData(32),
|
||||
signingPublicKey: ByteData(32),
|
||||
passwordKeys: WrappedKeys(
|
||||
nonce: ByteData(24),
|
||||
cipherText: ByteData(112),
|
||||
),
|
||||
needsAuthenticator: true,
|
||||
sessionsEndedBefore: endedBefore,
|
||||
);
|
||||
|
||||
final challenge = await endpoints.passwordSignIn.begin(
|
||||
notSignedIn(),
|
||||
address: 'anna@example.org',
|
||||
);
|
||||
final signedIn = await endpoints.passwordSignIn.complete(
|
||||
notSignedIn(),
|
||||
challenge: challenge.challenge,
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
||||
expect(signedIn.authUserId, anna);
|
||||
expect(
|
||||
await ServerSideSession.db.findById(session, annasPhone),
|
||||
isNull,
|
||||
);
|
||||
final left = await ServerSideSession.db.find(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(anna),
|
||||
);
|
||||
expect(left, hasLength(1));
|
||||
expect(left.single.createdAt.isBefore(endedBefore), isFalse);
|
||||
});
|
||||
|
||||
test('sign up at the register, the new account signed in here, '
|
||||
'identified', () async {
|
||||
final device = await PasswordDevice.make();
|
||||
|
|
|
|||
Loading…
Reference in a new issue