import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:isar/isar.dart'; import 'package:stack_wallet_backup/secure_storage.dart'; import 'package:stackwallet/models/isar/models/encrypted_string_value.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; abstract class SecureStorageInterface { dynamic get store; Future write({ required String key, required String? value, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }); Future read({ required String key, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }); Future delete({ required String key, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }); Future deleteAll({ IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }); } class DesktopSecureStore { final StorageCryptoHandler handler; late final Isar isar; DesktopSecureStore(this.handler); Future init() async { isar = await Isar.open( [EncryptedStringValueSchema], directory: (await StackFileSystem.applicationIsarDirectory()).path, inspector: false, name: "desktopStore", ); } Future close() async { await isar.close(); } Future read({ required String key, }) async { final value = await isar.encryptedStringValues.filter().keyEqualTo(key).findFirst(); // value does not exist; if (value == null) { return null; } return await handler.decryptValue(key, value.value); } Future write({ required String key, required String? value, }) async { if (value == null) { // here we assume that a value is to be deleted await isar.writeTxn(() async { await isar.encryptedStringValues.deleteByKey(key); }); } else { // otherwise created encrypted object value final object = EncryptedStringValue(); object.key = key; object.value = await handler.encryptValue(key, value); // store object value await isar.writeTxn(() async { await isar.encryptedStringValues.put(object); }); } } Future delete({ required String key, }) async { await isar.writeTxn(() async { await isar.encryptedStringValues.deleteByKey(key); }); } } /// all *Options params ignored on desktop class SecureStorageWrapper implements SecureStorageInterface { final dynamic _store; final bool _isDesktop; @override dynamic get store => _store; const SecureStorageWrapper({ required dynamic store, required bool isDesktop, }) : assert(isDesktop ? store is DesktopSecureStore : store is FlutterSecureStorage), _store = store, _isDesktop = isDesktop; @override Future read({ required String key, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }) async { if (_isDesktop) { return await (_store as DesktopSecureStore).read(key: key); } else { return await (_store as FlutterSecureStorage).read( key: key, iOptions: iOptions, aOptions: aOptions, lOptions: lOptions, webOptions: webOptions, mOptions: mOptions, wOptions: wOptions, ); } } @override Future write({ required String key, required String? value, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }) async { if (_isDesktop) { return await (_store as DesktopSecureStore).write(key: key, value: value); } else { return await (_store as FlutterSecureStorage).write( key: key, value: value, iOptions: iOptions, aOptions: aOptions, lOptions: lOptions, webOptions: webOptions, mOptions: mOptions, wOptions: wOptions, ); } } @override Future delete({ required String key, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }) async { if (_isDesktop) { return (_store as DesktopSecureStore).delete(key: key); } else { return await (_store as FlutterSecureStorage).delete( key: key, iOptions: iOptions, aOptions: aOptions, lOptions: lOptions, webOptions: webOptions, mOptions: mOptions, wOptions: wOptions, ); } } @override Future deleteAll({ IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }) async { if (_isDesktop) { // return (_store as DesktopSecureStore).deleteAll(); throw UnimplementedError(); } else { return await (_store as FlutterSecureStorage).deleteAll( iOptions: iOptions, aOptions: aOptions, lOptions: lOptions, webOptions: webOptions, mOptions: mOptions, wOptions: wOptions, ); } } } // Mock class for testing purposes class FakeSecureStorage implements SecureStorageInterface { final Map _store = {}; int _interactions = 0; int get interactions => _interactions; int _writes = 0; int get writes => _writes; int _reads = 0; int get reads => _reads; int _deletes = 0; int get deletes => _deletes; @override Future read({ required String key, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }) async { _interactions++; _reads++; return _store[key]; } @override Future write({ required String key, required String? value, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }) async { _interactions++; _writes++; _store[key] = value; } @override Future delete({ required String key, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }) async { _interactions++; _deletes++; _store.remove(key); } @override Future deleteAll({ IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, }) async { _interactions++; _deletes++; _store.clear(); } @override dynamic get store => throw UnimplementedError(); }