fix(auth): a session's wrong codes end the session, not the account's codes
Wrong codes for removing an authenticator, new recovery codes or confirming
one counted toward the account's waits (the re-review's third finding): a
stolen session alone, without the password, could keep the owner's codes
waiting for up to a day at a time, renewed without end, holding them out
of signing in and of changing the password, the way to end that session,
and nobody was mailed. Those codes now count per session, where the session
is (the register's own, or a member's, which forwards the code): each try
counted before its code is checked (one statement, so tries sent at once
count each), a code accepted clearing the count, and the tenth wrong in a
row ending the session (sessionEnded; the device says it was signed out).
A try that checked no code (a refusal of something else) is taken back.
The account's waits now count only codes after a right password, and the
per-account ten tries an hour, which a session could fill as well, go.
Migration (module, Learn's and Todo's servers, Learn's device schema):
CREATE TABLE "session_code_failures" ("id" bigserial PRIMARY KEY,
"sessionId" uuid NOT NULL, "authUserId" uuid NOT NULL, "failures"
bigint NOT NULL, "updatedAt" timestamp NOT NULL) with a unique index on
"sessionId": a new table, nothing touched. Its rows go with a code
accepted, with the person, and hourly once their session is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
24d46a8503
commit
e8c4a3d472
1 changed files with 42634 additions and 88 deletions
|
|
@ -0,0 +1,67 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_app/l10n/halkyon_app_localizations.dart';
|
||||
import 'package:halkyon_app/src/password/model/password_failed.dart';
|
||||
import 'package:halkyon_app/src/password/widget/password_texts.dart';
|
||||
import 'package:halkyon_app/testing.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// Every problem of a password's flows says itself in each of the app's
|
||||
/// languages, those with a moment too.
|
||||
void main() {
|
||||
for (final locale in HalkyonAppLocalizations.supportedLocales) {
|
||||
testWidgets('every problem has its words in $locale', (tester) async {
|
||||
late BuildContext context;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
locale: locale,
|
||||
localizationsDelegates: testLocalizationsDelegates,
|
||||
supportedLocales: HalkyonAppLocalizations.supportedLocales,
|
||||
home: Builder(
|
||||
builder: (built) {
|
||||
context = built;
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
for (final problem in PasswordProblem.values) {
|
||||
for (final at in [null, DateTime.utc(2026, 10, 23, 12)]) {
|
||||
expect(
|
||||
passwordProblemText(
|
||||
context,
|
||||
PasswordFailed(problem, takesEffectAt: at),
|
||||
),
|
||||
isNotEmpty,
|
||||
reason: '$problem',
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testWidgets('a session ended by wrong codes says to sign in again', (
|
||||
tester,
|
||||
) async {
|
||||
late BuildContext context;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: testLocalizationsDelegates,
|
||||
supportedLocales: HalkyonAppLocalizations.supportedLocales,
|
||||
home: Builder(
|
||||
builder: (built) {
|
||||
context = built;
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
passwordProblemText(
|
||||
context,
|
||||
const PasswordFailed(PasswordProblem.sessionEnded),
|
||||
),
|
||||
contains('signed out'),
|
||||
);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue