fix(auth): a member keeps the session a reset signed in when its notice comes late

A member remembers the session issueSessionAfter made, by the account and
the register's moment, and keeps it when the register's notice of that
moment arrives (the short reading's M2). The notice's moment, taken by the
member's clock, is later by its way there; when that took longer than the
reset's answer took to come back and the session to be made, the notice
ended the very session the reset had signed in, and the device had to sign
in again.

Kept in memory for a day, 10,000 at most: notices go out at once and
hourly until confirmed. A member restarted in between ends that session as
before. endSessionsHere takes a set of sessions to keep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-21 17:45:43 +02:00
commit b83fa04594
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1592,6 +1592,71 @@ void main() {
);
});
test("the register's notice of a reset, however long it took to "
'arrive, leaves the session made here for it; a notice of a '
'later moment ends it (the short reading, M2)', () async {
final endedBefore = DateTime.now().toUtc();
register
..proof = bensProof()
..endedBefore = endedBefore;
final signedIn = await resetBensPassword();
final made = (await ServerSideSession.db.find(
session,
where: (t) => t.authUserId.equals(ben),
)).single.id!;
expect(signedIn.authUserId, ben);
// Its way here took longer than the reset's answer: sent at once,
// it arrives after the session was made.
await Future<void>.delayed(const Duration(milliseconds: 20));
final answer = await answerSessionsEnded(
session,
jsonEncode({
'account': ben.uuid,
'endedBefore': endedBefore.toIso8601String(),
'sentAt': endedBefore.toIso8601String(),
}),
authorization: 'Bearer $_secret',
);
expect(answer.status, 200);
expect(await ServerSideSession.db.findById(session, made), isNotNull);
final later = DateTime.now().toUtc();
await answerSessionsEnded(
session,
jsonEncode({
'account': ben.uuid,
'endedBefore': later.toIso8601String(),
'sentAt': later.toIso8601String(),
}),
authorization: 'Bearer $_secret',
);
expect(await ServerSideSession.db.findById(session, made), isNull);
});
test("a notice of the reset's moment for another account keeps "
"nothing of Ben's", () async {
final endedBefore = DateTime.now().toUtc();
register
..proof = bensProof()
..endedBefore = endedBefore;
await resetBensPassword();
final annasTablet = await signInNewDevice(session, anna);
await Future<void>.delayed(const Duration(milliseconds: 5));
await answerSessionsEnded(
session,
jsonEncode({
'account': anna.uuid,
'endedBefore': endedBefore.toIso8601String(),
'sentAt': endedBefore.toIso8601String(),
}),
authorization: 'Bearer $_secret',
);
expect(
await ServerSideSession.db.findById(session, annasTablet),
isNull,
);
});
test("a clock ahead of the register's ends the sessions made here in "
"its gap too (the reading of the fixes' C1)", () async {
final now = DateTime.now().toUtc();