migrateLegacyPasswordHashes static method

Future<int> migrateLegacyPasswordHashes(
  1. Session session, {
  2. int batchSize = 100,
  3. int? maxMigratedEntries,
})

Migrates legacy password hashes to the latest hash algorithm.

batchSize is the number of entries to migrate in each batch.

maxMigratedEntries is the maximum number of entries that will be migrated. If null, all entries in the database will be migrated.

Returns the number of migrated entries.

Implementation

static Future<int> migrateLegacyPasswordHashes(
  Session session, {
  int batchSize = 100,
  int? maxMigratedEntries,
}) async {
  var updatedEntries = 0;
  int lastEntryId = 0;

  while (true) {
    var entries = await EmailAuth.db.find(
      session,
      where: (t) => t.hash.notLike(r'%$%') & (t.id > lastEntryId),
      orderBy: (t) => t.id,
      limit: batchSize,
    );

    if (entries.isEmpty) {
      return updatedEntries;
    }

    if (maxMigratedEntries != null) {
      if (maxMigratedEntries == updatedEntries) {
        return updatedEntries;
      }

      var entrySurplus =
          (updatedEntries + entries.length) - maxMigratedEntries;
      if (entrySurplus > 0) {
        entries = entries.sublist(0, entries.length - entrySurplus);
      }
    }

    lastEntryId = entries.last.id!;

    var migratedEntries = await Future.wait(entries.where((entry) {
      try {
        return PasswordHash(
          entry.hash,
          legacySalt: EmailSecrets.legacySalt,
        ).isLegacyHash();
      } catch (e) {
        session.log(
          'Error when checking if hash is legacy: $e',
          level: LogLevel.error,
        );
        return false;
      }
    }).map((entry) async {
      return entry.copyWith(
        hash: await PasswordHash.migratedLegacyToArgon2idHash(
          entry.hash,
          legacySalt: EmailSecrets.legacySalt,
          pepper: EmailSecrets.pepper,
          allowUnsecureRandom: AuthConfig.current.allowUnsecureRandom,
        ),
      );
    }).toList());

    try {
      await EmailAuth.db.update(session, migratedEntries);
      updatedEntries += migratedEntries.length;
    } catch (e) {
      session.log(
        'Failed to update migrated entries: $e',
        level: LogLevel.error,
      );
    }
  }
}