feat(app): the warning that a session was ended for its proofs

A password sign-in whose account carries the warning (a session ended
for ten wrong proofs in a row) shows it before the sign-in is put in
place: when, what it means, and to change the password if it wasn't
the owner. Dismiss clears it on the instance through the held-back
session; Remind me later signs in and keeps it. The account section
shows it above the change of password, with Dismiss, while it stands.
Nothing identifies the session or its device.

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 12:10:38 +02:00
commit c95b3d55b3
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -113,6 +113,52 @@ void main() {
expect(dependencies.addresses.addresses[anna.person], 'anna@example.org');
});
group('warns, before the app opens, that a session was ended for its '
'wrong proofs', () {
late FakePasswordAccount anna;
Future<void> signInAsAnna(WidgetTester tester) async {
anna = dependencies.passwordGateway.addAccount(
'anna@example.org',
password: 'correct horse',
crypto: dependencies.crypto,
)..sessionEndedForProofsAt = DateTime.utc(2026, 10, 19, 8, 30);
await start(tester);
await tester.tap(find.widgetWithText(FilledButton, 'Sign in'));
await tester.pumpAndSettle();
await tester.enterText(field('Email address'), 'anna@example.org');
await tester.enterText(field('Password'), 'correct horse');
await tapButton(tester, 'Continue');
await tester.enterText(
field('Code from your authenticator app'),
fakeTotpCode,
);
await tapButton(tester, 'Sign in');
}
testWidgets('saying when, then dismissed', (tester) async {
await signInAsAnna(tester);
expect(find.text('Someone tried your password'), findsOneWidget);
expect(find.textContaining('2026'), findsOneWidget);
expect(find.textContaining('change your password'), findsOneWidget);
expect(find.text('App of ${anna.person}'), findsNothing);
await tapButton(tester, 'Dismiss');
expect(find.text('App of ${anna.person}'), findsOneWidget);
expect(anna.sessionEndedForProofsAt, isNull);
});
testWidgets('or kept for later', (tester) async {
await signInAsAnna(tester);
await tester.tap(find.widgetWithText(TextButton, 'Remind me later'));
await tester.pumpAndSettle();
expect(find.text('App of ${anna.person}'), findsOneWidget);
expect(anna.sessionEndedForProofsAt, isNotNull);
});
});
testWidgets('an account never finished signs in with a code by mail, '
'then sets up its authenticator and keeps its recovery '
'codes', (tester) async {
@ -480,6 +526,35 @@ void main() {
expect(find.text('Change password'), findsNothing);
});
testWidgets('warns that a session was ended for its wrong proofs, until '
'dismissed', (tester) async {
final anna = dependencies.passwordGateway.addAccount(
'anna@example.org',
password: 'correct horse',
crypto: dependencies.crypto,
);
final passwords = dependencies.passwords;
await passwords.enter(
await passwords.signIn(
await passwords.unlock('anna@example.org', 'correct horse'),
totpCode: fakeTotpCode,
language: 'en',
),
);
anna.sessionEndedForProofsAt = DateTime.utc(2026, 10, 19, 8, 30);
await show(tester);
expect(find.text('Someone tried your password'), findsOneWidget);
expect(find.textContaining('change your password'), findsOneWidget);
// The change the warning suggests is right below it.
expect(find.text('Change password'), findsOneWidget);
await tester.tap(find.widgetWithText(TextButton, 'Dismiss'));
await tester.pumpAndSettle();
expect(find.text('Someone tried your password'), findsNothing);
expect(anna.sessionEndedForProofsAt, isNull);
});
testWidgets('once set up, changes the password and adds an authenticator '
'only with the password and a code from one before', (tester) async {
final anna = dependencies.passwordGateway.addAccount(