Whoever knew the password could guess codes at 10 an hour forever,
unnoticed, each guess checked against every authenticator (the second
reading's M4). Every code an account's second factor refuses after a
right password (sign-in, a phrase's reset, a change) or from a signed-in
device (removing an authenticator, new recovery codes, the code from one
confirmed before) now counts, per account, from every network; after
every tenth in a row no code is taken (codesWait, saying until when), an
hour, then twice as long each time, a day at the most (longestCodeWait,
second_factor_waits.dart): at a day, ten guesses a day still have about a
1 % chance a year against one authenticator, so the first wait mails the
owner that someone knows their password. The instance keeps no address,
so sign-in and the phrase's reset now send the address the device typed
(and its language), used only for that mail after its hash is checked
against the account's. A code accepted clears it all.
Migration (module, Learn's and Todo's servers, Learn's device schema):
CREATE TABLE "second_factor_failures" ("id" bigserial PRIMARY KEY,
"authUserId" uuid NOT NULL, "failures" bigint NOT NULL, "waits"
bigint NOT NULL, "waitUntil" timestamp, "updatedAt" timestamp NOT
NULL) with a unique index on "authUserId": a new table, nothing
touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
53 lines
2.4 KiB
Dart
53 lines
2.4 KiB
Dart
/// AUTOGENERATED BY SERVERPOD create-migration - DO NOT EDIT BY HAND
|
|
/// Merging: resolve registry conflicts the same as migration_registry.txt.
|
|
library;
|
|
|
|
import 'package:serverpod_database/serverpod_database.dart';
|
|
|
|
part '20260930094729362-studysets/migration.dart';
|
|
part '20260930122235111-cards/migration.dart';
|
|
part '20260930145136285-reviews/migration.dart';
|
|
part '20261001064926911-tree-tags-assets/migration.dart';
|
|
part '20261002191122122-notes/migration.dart';
|
|
part '20261002211346701-learning-paths/migration.dart';
|
|
part '20261003110855698-pdf-sources/migration.dart';
|
|
part '20261007205440868-card-buried-at/migration.dart';
|
|
part '20261008165051407-pdf-position/migration.dart';
|
|
part '20261009224606274-feedback/migration.dart';
|
|
part '20261012162411835-studyset-deleted-at/migration.dart';
|
|
part '20261019121406578-halkyon-base/migration.dart';
|
|
part '20261019201023295-profiles-in-halkyon-base/migration.dart';
|
|
part '20261019204112282-deletion-notices/migration.dart';
|
|
part '20261020111943300-passwords/migration.dart';
|
|
part '20261020125117584-sessions-ended/migration.dart';
|
|
part '20261020185750538-recovery-proof-key/migration.dart';
|
|
part '20261020194228286-second-factor-waits/migration.dart';
|
|
|
|
/// Migration registry for the client-side database.
|
|
class MigrationRegistry {
|
|
/// Ordered list of all client-side database migrations.
|
|
/// New migrations are always appended at the end.
|
|
static final List<MigrationVersionSql> migrations = [
|
|
_Migration20260930094729362_studysets(),
|
|
_Migration20260930122235111_cards(),
|
|
_Migration20260930145136285_reviews(),
|
|
_Migration20261001064926911_tree_tags_assets(),
|
|
_Migration20261002191122122_notes(),
|
|
_Migration20261002211346701_learning_paths(),
|
|
_Migration20261003110855698_pdf_sources(),
|
|
_Migration20261007205440868_card_buried_at(),
|
|
_Migration20261008165051407_pdf_position(),
|
|
_Migration20261009224606274_feedback(),
|
|
_Migration20261012162411835_studyset_deleted_at(),
|
|
_Migration20261019121406578_halkyon_base(),
|
|
_Migration20261019201023295_profiles_in_halkyon_base(),
|
|
_Migration20261019204112282_deletion_notices(),
|
|
_Migration20261020111943300_passwords(),
|
|
_Migration20261020125117584_sessions_ended(),
|
|
_Migration20261020185750538_recovery_proof_key(),
|
|
_Migration20261020194228286_second_factor_waits(),
|
|
];
|
|
|
|
/// List of all client-side database migration versions.
|
|
static List<String> get versions => migrations.map((m) => m.version).toList();
|
|
}
|