ente/auth/lib/store/code_store.dart

201 lines
6.1 KiB
Dart
Raw Normal View History

2022-11-01 06:13:06 +00:00
import 'dart:convert';
2023-07-31 10:17:54 +00:00
import 'package:collection/collection.dart';
import 'package:ente_auth/core/configuration.dart';
2022-11-01 06:13:06 +00:00
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/events/codes_updated_event.dart';
import 'package:ente_auth/models/authenticator/entity_result.dart';
2022-11-01 06:13:06 +00:00
import 'package:ente_auth/models/code.dart';
import 'package:ente_auth/services/authenticator_service.dart';
import 'package:ente_auth/store/offline_authenticator_db.dart';
import 'package:logging/logging.dart';
2022-11-01 06:13:06 +00:00
class CodeStore {
static final CodeStore instance = CodeStore._privateConstructor();
CodeStore._privateConstructor();
late AuthenticatorService _authenticatorService;
final _logger = Logger("CodeStore");
2022-11-01 06:13:06 +00:00
Future<void> init() async {
_authenticatorService = AuthenticatorService.instance;
}
2024-05-15 07:54:48 +00:00
Future<List<Code>> getAllCodes({
AccountMode? accountMode,
bool sortCodes = true,
}) async {
2023-09-04 10:31:59 +00:00
final mode = accountMode ?? _authenticatorService.getAccountMode();
final List<EntityResult> entities =
2023-09-04 10:31:59 +00:00
await _authenticatorService.getEntities(mode);
2024-05-14 18:21:43 +00:00
final List<Code> codes = [];
for (final entity in entities) {
2024-05-07 14:35:19 +00:00
late Code code;
2024-05-09 10:26:36 +00:00
try {
final decodeJson = jsonDecode(entity.rawData);
if (decodeJson is String && decodeJson.startsWith('otpauth://')) {
code = Code.fromOTPAuthUrl(decodeJson);
} else {
code = Code.fromExportJson(decodeJson);
}
2024-05-21 07:29:39 +00:00
} catch (e, s) {
2024-05-09 10:26:36 +00:00
code = Code.withError(e, entity.rawData);
2024-05-21 07:29:39 +00:00
_logger.severe("Could not parse code", e, s);
2024-04-26 08:47:12 +00:00
}
2024-05-07 14:35:19 +00:00
code.generatedID = entity.generatedID;
code.hasSynced = entity.hasSynced;
codes.add(code);
2022-11-01 06:13:06 +00:00
}
2023-05-11 08:48:11 +00:00
2024-05-15 07:54:48 +00:00
if (sortCodes) {
// sort codes by issuer,account
codes.sort((firstCode, secondCode) {
if (secondCode.isPinned && !firstCode.isPinned) return 1;
if (!secondCode.isPinned && firstCode.isPinned) return -1;
2024-04-29 10:11:17 +00:00
2024-05-15 07:54:48 +00:00
final issuerComparison =
compareAsciiLowerCaseNatural(firstCode.issuer, secondCode.issuer);
if (issuerComparison != 0) {
return issuerComparison;
}
return compareAsciiLowerCaseNatural(
firstCode.account,
secondCode.account,
);
});
}
2024-05-09 10:26:36 +00:00
return codes;
2022-11-01 06:13:06 +00:00
}
2023-09-05 09:18:23 +00:00
Future<AddResult> addCode(
Code code, {
bool shouldSync = true,
2023-09-04 10:31:59 +00:00
AccountMode? accountMode,
}) async {
final mode = accountMode ?? _authenticatorService.getAccountMode();
final allCodes = await getAllCodes(accountMode: mode);
2022-11-22 07:19:39 +00:00
bool isExistingCode = false;
2024-04-26 08:47:12 +00:00
bool hasSameCode = false;
2024-05-09 10:26:36 +00:00
for (final existingCode in allCodes) {
if (existingCode.hasError) continue;
2024-04-26 08:47:12 +00:00
if (code.generatedID != null &&
existingCode.generatedID == code.generatedID) {
2022-11-22 07:19:39 +00:00
isExistingCode = true;
break;
}
2024-04-26 08:47:12 +00:00
if (existingCode == code) {
hasSameCode = true;
}
}
if (!isExistingCode && hasSameCode) {
return AddResult.duplicate;
}
2023-09-05 09:18:23 +00:00
late AddResult result;
2022-11-22 07:19:39 +00:00
if (isExistingCode) {
2023-09-05 09:18:23 +00:00
result = AddResult.updateCode;
2022-11-22 07:19:39 +00:00
await _authenticatorService.updateEntry(
2022-11-22 08:07:09 +00:00
code.generatedID!,
2024-05-07 13:56:17 +00:00
code.toOTPAuthUrlFormat(),
2022-11-22 07:19:39 +00:00
shouldSync,
2023-09-04 10:31:59 +00:00
mode,
2022-11-22 07:19:39 +00:00
);
} else {
2023-09-05 09:18:23 +00:00
result = AddResult.newCode;
2022-11-22 08:07:09 +00:00
code.generatedID = await _authenticatorService.addEntry(
2024-05-07 13:56:17 +00:00
code.toOTPAuthUrlFormat(),
2022-11-22 07:19:39 +00:00
shouldSync,
2023-09-04 10:31:59 +00:00
mode,
2022-11-22 07:19:39 +00:00
);
}
2022-11-01 06:13:06 +00:00
Bus.instance.fire(CodesUpdatedEvent());
2023-09-05 09:18:23 +00:00
return result;
2022-11-01 06:13:06 +00:00
}
2023-09-04 10:31:59 +00:00
Future<void> removeCode(Code code, {AccountMode? accountMode}) async {
final mode = accountMode ?? _authenticatorService.getAccountMode();
await _authenticatorService.deleteEntry(code.generatedID!, mode);
2022-11-01 06:13:06 +00:00
Bus.instance.fire(CodesUpdatedEvent());
}
bool _isOfflineImportRunning = false;
Future<void> importOfflineCodes() async {
2024-04-26 08:47:12 +00:00
if (_isOfflineImportRunning) {
return;
}
_isOfflineImportRunning = true;
2023-09-05 09:18:23 +00:00
Logger logger = Logger('importOfflineCodes');
try {
Configuration config = Configuration.instance;
2023-09-05 02:51:11 +00:00
if (!config.hasConfiguredAccount() ||
!config.hasOptedForOfflineMode() ||
config.getOfflineSecretKey() == null) {
return;
}
2023-09-05 09:18:23 +00:00
logger.info('start import');
2023-09-05 02:51:11 +00:00
2024-04-29 10:11:17 +00:00
List<Code> offlineCodes = (await CodeStore.instance
.getAllCodes(accountMode: AccountMode.offline))
2024-05-09 10:26:36 +00:00
.where((element) => !element.hasError)
.toList();
if (offlineCodes.isEmpty) {
2023-09-05 02:51:11 +00:00
return;
}
2023-09-05 02:42:52 +00:00
bool isOnlineSyncDone = await AuthenticatorService.instance.onlineSync();
if (!isOnlineSyncDone) {
2023-09-05 09:18:23 +00:00
logger.info("skip as online sync is not done");
2023-09-05 02:42:52 +00:00
return;
}
2024-04-29 10:11:17 +00:00
final List<Code> onlineCodes = (await CodeStore.instance
.getAllCodes(accountMode: AccountMode.online))
2024-05-09 10:26:36 +00:00
.where((element) => !element.hasError)
.toList();
2023-09-05 09:18:23 +00:00
logger.info(
'importing ${offlineCodes.length} offline codes with ${onlineCodes.length} online codes',
);
for (Code eachCode in offlineCodes) {
bool alreadyPresent = onlineCodes.any(
(oc) =>
oc.issuer == eachCode.issuer &&
oc.account == eachCode.account &&
oc.secret == eachCode.secret,
);
int? generatedID = eachCode.generatedID!;
2023-09-05 09:18:23 +00:00
logger.info(
'importingCode: genID ${eachCode.generatedID} & isAlreadyPresent $alreadyPresent',
);
if (!alreadyPresent) {
// Avoid conflict with generatedID of online codes
eachCode.generatedID = null;
2023-09-05 09:18:23 +00:00
final AddResult result = await CodeStore.instance.addCode(
eachCode,
accountMode: AccountMode.online,
shouldSync: false,
);
2023-09-05 09:18:23 +00:00
logger.info(
'importedCode: genID ${eachCode.generatedID} result: ${result.name}',
);
}
2023-09-05 02:42:52 +00:00
await OfflineAuthenticatorDB.instance.deleteByIDs(
generatedIDs: [generatedID],
2023-09-05 02:42:52 +00:00
);
}
AuthenticatorService.instance.onlineSync().ignore();
} catch (e, s) {
_logger.severe("error while importing offline codes", e, s);
} finally {
_isOfflineImportRunning = false;
}
}
2022-11-01 06:13:06 +00:00
}
2023-09-05 09:18:23 +00:00
enum AddResult {
newCode,
duplicate,
updateCode,
}