From 71e97ba141622d26502e9bbd30ffa467c902e6bb Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Sat, 12 Jul 2025 17:43:11 +0530 Subject: [PATCH] Packages --- mobile/packages/.gitignore | 1 + mobile/packages/base/analysis_options.yaml | 10 + mobile/packages/base/lib/ente_base.dart | 6 + mobile/packages/base/lib/models/database.dart | 3 + .../base/lib/models/key_attributes.dart | 102 ++++ .../base/lib/models/key_gen_result.dart | 12 + .../lib/models/private_key_attributes.dart | 7 + mobile/packages/base/pubspec.lock | 205 ++++++++ mobile/packages/base/pubspec.yaml | 18 + .../configuration/lib/configuration.dart | 403 +++++++++++++- .../packages/configuration/lib/constants.dart | 5 + mobile/packages/configuration/pubspec.lock | 495 +++++++++++++++++- mobile/packages/configuration/pubspec.yaml | 15 + mobile/packages/events/analysis_options.yaml | 10 + mobile/packages/events/lib/ente_events.dart | 13 + mobile/packages/events/lib/event_bus.dart | 5 + .../lib/models/collections_updated_event.dart | 3 + .../lib/models/endpoint_updated_event.dart | 3 + mobile/packages/events/lib/models/event.dart | 1 + .../events/lib/models/signed_in_event.dart | 3 + .../events/lib/models/signed_out_event.dart | 3 + .../lib/models/trigger_logout_event.dart | 3 + .../models/user_details_changed_event.dart | 3 + mobile/packages/events/pubspec.lock | 213 ++++++++ mobile/packages/events/pubspec.yaml | 19 + mobile/packages/logging/analysis_options.yaml | 10 + mobile/packages/logging/lib/logging.dart | 3 + .../logging/lib/src/super_logging.dart | 402 ++++++++++++++ .../logging/lib/src/tunneled_transport.dart | 140 +++++ mobile/packages/logging/pubspec.lock | 474 +++++++++++++++++ mobile/packages/logging/pubspec.yaml | 28 + 31 files changed, 2614 insertions(+), 4 deletions(-) create mode 100644 mobile/packages/base/analysis_options.yaml create mode 100644 mobile/packages/base/lib/ente_base.dart create mode 100644 mobile/packages/base/lib/models/database.dart create mode 100644 mobile/packages/base/lib/models/key_attributes.dart create mode 100644 mobile/packages/base/lib/models/key_gen_result.dart create mode 100644 mobile/packages/base/lib/models/private_key_attributes.dart create mode 100644 mobile/packages/base/pubspec.lock create mode 100644 mobile/packages/base/pubspec.yaml create mode 100644 mobile/packages/configuration/lib/constants.dart create mode 100644 mobile/packages/events/analysis_options.yaml create mode 100644 mobile/packages/events/lib/ente_events.dart create mode 100644 mobile/packages/events/lib/event_bus.dart create mode 100644 mobile/packages/events/lib/models/collections_updated_event.dart create mode 100644 mobile/packages/events/lib/models/endpoint_updated_event.dart create mode 100644 mobile/packages/events/lib/models/event.dart create mode 100644 mobile/packages/events/lib/models/signed_in_event.dart create mode 100644 mobile/packages/events/lib/models/signed_out_event.dart create mode 100644 mobile/packages/events/lib/models/trigger_logout_event.dart create mode 100644 mobile/packages/events/lib/models/user_details_changed_event.dart create mode 100644 mobile/packages/events/pubspec.lock create mode 100644 mobile/packages/events/pubspec.yaml create mode 100644 mobile/packages/logging/analysis_options.yaml create mode 100644 mobile/packages/logging/lib/logging.dart create mode 100644 mobile/packages/logging/lib/src/super_logging.dart create mode 100644 mobile/packages/logging/lib/src/tunneled_transport.dart create mode 100644 mobile/packages/logging/pubspec.lock create mode 100644 mobile/packages/logging/pubspec.yaml diff --git a/mobile/packages/.gitignore b/mobile/packages/.gitignore index cb3e7cf51b..009c454944 100644 --- a/mobile/packages/.gitignore +++ b/mobile/packages/.gitignore @@ -1 +1,2 @@ .dart_tool/ +.flutter-plugins-dependencies diff --git a/mobile/packages/base/analysis_options.yaml b/mobile/packages/base/analysis_options.yaml new file mode 100644 index 0000000000..609eb5d8aa --- /dev/null +++ b/mobile/packages/base/analysis_options.yaml @@ -0,0 +1,10 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + - always_declare_return_types + - always_put_required_named_parameters_first + - avoid_print + - prefer_const_constructors + - prefer_const_literals_to_create_immutables + - use_key_in_widget_constructors diff --git a/mobile/packages/base/lib/ente_base.dart b/mobile/packages/base/lib/ente_base.dart new file mode 100644 index 0000000000..6f9e6961da --- /dev/null +++ b/mobile/packages/base/lib/ente_base.dart @@ -0,0 +1,6 @@ +library ente_base; + +export 'models/database.dart'; +export 'models/key_attributes.dart'; +export 'models/key_gen_result.dart'; +export 'models/private_key_attributes.dart'; diff --git a/mobile/packages/base/lib/models/database.dart b/mobile/packages/base/lib/models/database.dart new file mode 100644 index 0000000000..61216d45e1 --- /dev/null +++ b/mobile/packages/base/lib/models/database.dart @@ -0,0 +1,3 @@ +abstract class EnteBaseDatabase { + Future clearTable(); +} diff --git a/mobile/packages/base/lib/models/key_attributes.dart b/mobile/packages/base/lib/models/key_attributes.dart new file mode 100644 index 0000000000..8b8ec3990f --- /dev/null +++ b/mobile/packages/base/lib/models/key_attributes.dart @@ -0,0 +1,102 @@ +import 'dart:convert'; + +class KeyAttributes { + final String kekSalt; + final String encryptedKey; + final String keyDecryptionNonce; + final String publicKey; + final String encryptedSecretKey; + final String secretKeyDecryptionNonce; + final int memLimit; + final int opsLimit; + final String masterKeyEncryptedWithRecoveryKey; + final String masterKeyDecryptionNonce; + final String recoveryKeyEncryptedWithMasterKey; + final String recoveryKeyDecryptionNonce; + + KeyAttributes( + this.kekSalt, + this.encryptedKey, + this.keyDecryptionNonce, + this.publicKey, + this.encryptedSecretKey, + this.secretKeyDecryptionNonce, + this.memLimit, + this.opsLimit, + this.masterKeyEncryptedWithRecoveryKey, + this.masterKeyDecryptionNonce, + this.recoveryKeyEncryptedWithMasterKey, + this.recoveryKeyDecryptionNonce, + ); + + Map toMap() { + return { + 'kekSalt': kekSalt, + 'encryptedKey': encryptedKey, + 'keyDecryptionNonce': keyDecryptionNonce, + 'publicKey': publicKey, + 'encryptedSecretKey': encryptedSecretKey, + 'secretKeyDecryptionNonce': secretKeyDecryptionNonce, + 'memLimit': memLimit, + 'opsLimit': opsLimit, + 'masterKeyEncryptedWithRecoveryKey': masterKeyEncryptedWithRecoveryKey, + 'masterKeyDecryptionNonce': masterKeyDecryptionNonce, + 'recoveryKeyEncryptedWithMasterKey': recoveryKeyEncryptedWithMasterKey, + 'recoveryKeyDecryptionNonce': recoveryKeyDecryptionNonce, + }; + } + + factory KeyAttributes.fromMap(Map map) { + return KeyAttributes( + map['kekSalt'], + map['encryptedKey'], + map['keyDecryptionNonce'], + map['publicKey'], + map['encryptedSecretKey'], + map['secretKeyDecryptionNonce'], + map['memLimit'], + map['opsLimit'], + map['masterKeyEncryptedWithRecoveryKey'], + map['masterKeyDecryptionNonce'], + map['recoveryKeyEncryptedWithMasterKey'], + map['recoveryKeyDecryptionNonce'], + ); + } + + String toJson() => json.encode(toMap()); + + factory KeyAttributes.fromJson(String source) => + KeyAttributes.fromMap(json.decode(source)); + + KeyAttributes copyWith({ + String? kekSalt, + String? encryptedKey, + String? keyDecryptionNonce, + String? publicKey, + String? encryptedSecretKey, + String? secretKeyDecryptionNonce, + int? memLimit, + int? opsLimit, + String? masterKeyEncryptedWithRecoveryKey, + String? masterKeyDecryptionNonce, + String? recoveryKeyEncryptedWithMasterKey, + String? recoveryKeyDecryptionNonce, + }) { + return KeyAttributes( + kekSalt ?? this.kekSalt, + encryptedKey ?? this.encryptedKey, + keyDecryptionNonce ?? this.keyDecryptionNonce, + publicKey ?? this.publicKey, + encryptedSecretKey ?? this.encryptedSecretKey, + secretKeyDecryptionNonce ?? this.secretKeyDecryptionNonce, + memLimit ?? this.memLimit, + opsLimit ?? this.opsLimit, + masterKeyEncryptedWithRecoveryKey ?? + this.masterKeyEncryptedWithRecoveryKey, + masterKeyDecryptionNonce ?? this.masterKeyDecryptionNonce, + recoveryKeyEncryptedWithMasterKey ?? + this.recoveryKeyEncryptedWithMasterKey, + recoveryKeyDecryptionNonce ?? this.recoveryKeyDecryptionNonce, + ); + } +} diff --git a/mobile/packages/base/lib/models/key_gen_result.dart b/mobile/packages/base/lib/models/key_gen_result.dart new file mode 100644 index 0000000000..27dd062229 --- /dev/null +++ b/mobile/packages/base/lib/models/key_gen_result.dart @@ -0,0 +1,12 @@ +import "dart:typed_data"; + +import 'key_attributes.dart'; +import 'private_key_attributes.dart'; + +class KeyGenResult { + final KeyAttributes keyAttributes; + final PrivateKeyAttributes privateKeyAttributes; + final Uint8List loginKey; + + KeyGenResult(this.keyAttributes, this.privateKeyAttributes, this.loginKey); +} diff --git a/mobile/packages/base/lib/models/private_key_attributes.dart b/mobile/packages/base/lib/models/private_key_attributes.dart new file mode 100644 index 0000000000..c92f017fc6 --- /dev/null +++ b/mobile/packages/base/lib/models/private_key_attributes.dart @@ -0,0 +1,7 @@ +class PrivateKeyAttributes { + final String key; + final String recoveryKey; + final String secretKey; + + PrivateKeyAttributes(this.key, this.recoveryKey, this.secretKey); +} diff --git a/mobile/packages/base/pubspec.lock b/mobile/packages/base/pubspec.lock new file mode 100644 index 0000000000..6bbc3fcd93 --- /dev/null +++ b/mobile/packages/base/pubspec.lock @@ -0,0 +1,205 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/mobile/packages/base/pubspec.yaml b/mobile/packages/base/pubspec.yaml new file mode 100644 index 0000000000..04aeb70512 --- /dev/null +++ b/mobile/packages/base/pubspec.yaml @@ -0,0 +1,18 @@ +name: ente_base +description: A base Flutter package containing shared models and types for ente apps +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/mobile/packages/configuration/lib/configuration.dart b/mobile/packages/configuration/lib/configuration.dart index cd3ad1803b..bfe8c5ef43 100644 --- a/mobile/packages/configuration/lib/configuration.dart +++ b/mobile/packages/configuration/lib/configuration.dart @@ -1,7 +1,406 @@ library configuration; +import 'dart:convert'; +import 'dart:io' as io; +import 'dart:typed_data'; + +import 'package:bip39/bip39.dart' as bip39; +import 'package:configuration/constants.dart'; +import 'package:ente_base/models/database.dart'; +import 'package:ente_base/models/key_attributes.dart'; +import 'package:ente_base/models/key_gen_result.dart'; +import 'package:ente_base/models/private_key_attributes.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_events/event_bus.dart'; +import 'package:ente_events/models/endpoint_updated_event.dart'; +import 'package:ente_events/models/signed_in_event.dart'; +import 'package:ente_events/models/signed_out_event.dart'; +import 'package:ente_logging/logging.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tuple/tuple.dart'; + class Configuration { - static Future init() async { - // TODO + Configuration._privateConstructor(); + + static final Configuration instance = Configuration._privateConstructor(); + static const endpoint = String.fromEnvironment( + "endpoint", + defaultValue: kDefaultProductionEndpoint, + ); + static const emailKey = "email"; + static const keyAttributesKey = "key_attributes"; + + static const lastTempFolderClearTimeKey = "last_temp_folder_clear_time"; + static const keyKey = "key"; + static const secretKeyKey = "secret_key"; + static const authSecretKeyKey = "auth_secret_key"; + static const offlineAuthSecretKey = "offline_auth_secret_key"; + static const tokenKey = "token"; + static const encryptedTokenKey = "encrypted_token"; + static const userIDKey = "user_id"; + static const endPointKey = "endpoint"; + + final kTempFolderDeletionTimeBuffer = const Duration(days: 1).inMicroseconds; + + static final _logger = Logger("Configuration"); + + String? _cachedToken; + late SharedPreferences _preferences; + String? _key; + String? _secretKey; + late FlutterSecureStorage _secureStorage; + late String _documentsDirectory; + late String _cacheDirectory; + late String _tempDocumentsDirPath; + late List _databases; + + String? _volatilePassword; + + Future init(List dbs) async { + _databases = dbs; + _documentsDirectory = (await getApplicationDocumentsDirectory()).path; + _tempDocumentsDirPath = "$_documentsDirectory/temp/"; + _preferences = await SharedPreferences.getInstance(); + _secureStorage = const FlutterSecureStorage( + iOptions: IOSOptions( + accessibility: KeychainAccessibility.first_unlock_this_device, + ), + ); + final tempDirectory = io.Directory(_tempDocumentsDirPath); + try { + final currentTime = DateTime.now().microsecondsSinceEpoch; + if (tempDirectory.existsSync() && + (_preferences.getInt(lastTempFolderClearTimeKey) ?? 0) < + (currentTime - kTempFolderDeletionTimeBuffer)) { + await tempDirectory.delete(recursive: true); + await _preferences.setInt(lastTempFolderClearTimeKey, currentTime); + _logger.info("Cleared temp folder"); + } else { + _logger.info("Skipping temp folder clear"); + } + } catch (e) { + _logger.warning(e); + } + tempDirectory.createSync(recursive: true); + + _cacheDirectory = "$_documentsDirectory/cache/"; + if (!io.Directory(_cacheDirectory).existsSync()) { + io.Directory(_cacheDirectory).createSync(recursive: true); + } + } + + Future logout({bool autoLogout = false}) async { + await _preferences.clear(); + _secureStorage.deleteAll(); + for (final db in _databases) { + await db.clearTable(); + } + _key = null; + _cachedToken = null; + _secretKey = null; + Bus.instance.fire(SignedOutEvent()); + } + + Future generateKey(String password) async { + // Create a master key + final masterKey = CryptoUtil.generateKey(); + + // Create a recovery key + final recoveryKey = CryptoUtil.generateKey(); + + // Encrypt master key and recovery key with each other + final encryptedMasterKey = CryptoUtil.encryptSync(masterKey, recoveryKey); + final encryptedRecoveryKey = CryptoUtil.encryptSync(recoveryKey, masterKey); + + // Derive a key from the password that will be used to encrypt and + // decrypt the master key + final kekSalt = CryptoUtil.getSaltToDeriveKey(); + final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( + utf8.encode(password), + kekSalt, + ); + final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); + + // Encrypt the key with this derived key + final encryptedKeyData = + CryptoUtil.encryptSync(masterKey, derivedKeyResult.key); + + // Generate a public-private keypair and encrypt the latter + final keyPair = CryptoUtil.generateKeyPair(); + final encryptedSecretKeyData = + CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey); + + final attributes = KeyAttributes( + CryptoUtil.bin2base64(kekSalt), + CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), + CryptoUtil.bin2base64(encryptedKeyData.nonce!), + CryptoUtil.bin2base64(keyPair.publicKey), + CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!), + CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!), + derivedKeyResult.memLimit, + derivedKeyResult.opsLimit, + CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!), + CryptoUtil.bin2base64(encryptedMasterKey.nonce!), + CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!), + CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!), + ); + final privateAttributes = PrivateKeyAttributes( + CryptoUtil.bin2base64(masterKey), + CryptoUtil.bin2hex(recoveryKey), + CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()), + ); + return KeyGenResult(attributes, privateAttributes, loginKey); + } + + Future> getAttributesForNewPassword( + String password, + ) async { + // Get master key + final masterKey = getKey(); + + // Derive a key from the password that will be used to encrypt and + // decrypt the master key + final kekSalt = CryptoUtil.getSaltToDeriveKey(); + final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( + utf8.encode(password), + kekSalt, + ); + final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); + + // Encrypt the key with this derived key + final encryptedKeyData = + CryptoUtil.encryptSync(masterKey!, derivedKeyResult.key); + + final existingAttributes = getKeyAttributes(); + + final updatedAttributes = existingAttributes!.copyWith( + kekSalt: CryptoUtil.bin2base64(kekSalt), + encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), + keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!), + memLimit: derivedKeyResult.memLimit, + opsLimit: derivedKeyResult.opsLimit, + ); + return Tuple2(updatedAttributes, loginKey); + } + + // decryptSecretsAndGetLoginKey decrypts the master key and recovery key + // with the given password and save them in local secure storage. + // This method also returns the keyEncKey that can be used for performing + // SRP setup for existing users. + Future decryptSecretsAndGetKeyEncKey( + String password, + KeyAttributes attributes, { + Uint8List? keyEncryptionKey, + }) async { + _logger.info('Start decryptAndSaveSecrets'); + keyEncryptionKey ??= await CryptoUtil.deriveKey( + utf8.encode(password), + CryptoUtil.base642bin(attributes.kekSalt), + attributes.memLimit, + attributes.opsLimit, + ); + + _logger.info('user-key done'); + Uint8List key; + try { + key = CryptoUtil.decryptSync( + CryptoUtil.base642bin(attributes.encryptedKey), + keyEncryptionKey, + CryptoUtil.base642bin(attributes.keyDecryptionNonce), + ); + } catch (e) { + _logger.severe('master-key failed, incorrect password?', e); + throw Exception("Incorrect password"); + } + _logger.info("master-key done"); + await setKey(CryptoUtil.bin2base64(key)); + final secretKey = CryptoUtil.decryptSync( + CryptoUtil.base642bin(attributes.encryptedSecretKey), + key, + CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce), + ); + _logger.info("secret-key done"); + await setSecretKey(CryptoUtil.bin2base64(secretKey)); + final token = CryptoUtil.openSealSync( + CryptoUtil.base642bin(getEncryptedToken()!), + CryptoUtil.base642bin(attributes.publicKey), + secretKey, + ); + _logger.info('appToken done'); + await setToken( + CryptoUtil.bin2base64(token, urlSafe: true), + ); + return keyEncryptionKey; + } + + Future recover(String recoveryKey) async { + // check if user has entered mnemonic code + if (recoveryKey.contains(' ')) { + final split = recoveryKey.split(' '); + if (split.length != mnemonicKeyWordCount) { + String wordThatIsFollowedByEmptySpaceInSplit = ''; + for (int i = 0; i < split.length; i++) { + String word = split[i]; + if (word.isEmpty) { + wordThatIsFollowedByEmptySpaceInSplit = + '\n\nExtra space after word at position $i'; + break; + } + } + throw AssertionError( + '\nRecovery code should have $mnemonicKeyWordCount words, ' + 'found ${split.length} words instead.$wordThatIsFollowedByEmptySpaceInSplit', + ); + } + recoveryKey = bip39.mnemonicToEntropy(recoveryKey); + } + final attributes = getKeyAttributes(); + Uint8List masterKey; + try { + masterKey = await CryptoUtil.decrypt( + CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey), + CryptoUtil.hex2bin(recoveryKey), + CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce), + ); + } catch (e) { + _logger.severe(e); + rethrow; + } + await setKey(CryptoUtil.bin2base64(masterKey)); + final secretKey = CryptoUtil.decryptSync( + CryptoUtil.base642bin(attributes.encryptedSecretKey), + masterKey, + CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce), + ); + await setSecretKey(CryptoUtil.bin2base64(secretKey)); + final token = CryptoUtil.openSealSync( + CryptoUtil.base642bin(getEncryptedToken()!), + CryptoUtil.base642bin(attributes.publicKey), + secretKey, + ); + await setToken( + CryptoUtil.bin2base64(token, urlSafe: true), + ); + } + + String getHttpEndpoint() { + return _preferences.getString(endPointKey) ?? endpoint; + } + + Future setHttpEndpoint(String endpoint) async { + await _preferences.setString(endPointKey, endpoint); + Bus.instance.fire(EndpointUpdatedEvent()); + } + + String? getToken() { + _cachedToken ??= _preferences.getString(tokenKey); + return _cachedToken; + } + + bool isLoggedIn() { + return getToken() != null; + } + + Future setToken(String token) async { + _cachedToken = token; + await _preferences.setString(tokenKey, token); + Bus.instance.fire(SignedInEvent()); + } + + Future setEncryptedToken(String encryptedToken) async { + await _preferences.setString(encryptedTokenKey, encryptedToken); + } + + String? getEncryptedToken() { + return _preferences.getString(encryptedTokenKey); + } + + String? getEmail() { + return _preferences.getString(emailKey); + } + + Future setEmail(String email) async { + await _preferences.setString(emailKey, email); + } + + int? getUserID() { + return _preferences.getInt(userIDKey); + } + + Future setUserID(int userID) async { + await _preferences.setInt(userIDKey, userID); + } + + Future setKeyAttributes(KeyAttributes attributes) async { + await _preferences.setString(keyAttributesKey, attributes.toJson()); + } + + KeyAttributes? getKeyAttributes() { + final jsonValue = _preferences.getString(keyAttributesKey); + if (jsonValue == null) { + return null; + } else { + return KeyAttributes.fromJson(jsonValue); + } + } + + Future setKey(String key) async { + _key = key; + await _secureStorage.write( + key: keyKey, + value: key, + ); + } + + Future setSecretKey(String? secretKey) async { + _secretKey = secretKey; + await _secureStorage.write( + key: secretKeyKey, + value: secretKey, + ); + } + + Uint8List? getKey() { + return _key == null ? null : CryptoUtil.base642bin(_key!); + } + + Uint8List? getSecretKey() { + return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!); + } + + Uint8List getRecoveryKey() { + final keyAttributes = getKeyAttributes()!; + return CryptoUtil.decryptSync( + CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey), + getKey()!, + CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce), + ); + } + + // Caution: This directory is cleared on app start + String getTempDirectory() { + return _tempDocumentsDirPath; + } + + String getCacheDirectory() { + return _cacheDirectory; + } + + bool hasConfiguredAccount() { + return getToken() != null && _key != null; + } + + void setVolatilePassword(String volatilePassword) { + _volatilePassword = volatilePassword; + } + + void resetVolatilePassword() { + _volatilePassword = null; + } + + String? getVolatilePassword() { + return _volatilePassword; } } diff --git a/mobile/packages/configuration/lib/constants.dart b/mobile/packages/configuration/lib/constants.dart new file mode 100644 index 0000000000..7b28375d9f --- /dev/null +++ b/mobile/packages/configuration/lib/constants.dart @@ -0,0 +1,5 @@ +const kDefaultProductionEndpoint = 'https://api.ente.io'; + +// 256 bit key maps to 24 words +// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#Generating_the_mnemonic +const mnemonicKeyWordCount = 24; diff --git a/mobile/packages/configuration/pubspec.lock b/mobile/packages/configuration/pubspec.lock index 6bbc3fcd93..11868bfed4 100644 --- a/mobile/packages/configuration/pubspec.lock +++ b/mobile/packages/configuration/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -9,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + bip39: + dependency: "direct main" + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" boolean_selector: dependency: transitive description: @@ -41,6 +57,84 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.dev" + source: hosted + version: "7.0.3" + ente_base: + dependency: "direct main" + description: + path: "../base" + relative: true + source: path + version: "1.0.0" + ente_crypto_dart: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: f91e1545f8263df127762240c4da54a0c42835b2 + url: "https://github.com/ente-io/ente_crypto_dart.git" + source: git + version: "1.0.0" + ente_events: + dependency: "direct main" + description: + path: "../events" + relative: true + source: path + version: "1.0.0" + ente_logging: + dependency: "direct main" + description: + path: "../logging" + relative: true + source: path + version: "1.0.0" + event_bus: + dependency: transitive + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" fake_async: dependency: transitive description: @@ -49,6 +143,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -62,11 +180,128 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -99,6 +334,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -123,6 +366,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" path: dependency: transitive description: @@ -131,11 +390,171 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + sentry: + dependency: transitive + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: transitive + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" + url: "https://pub.dev" + source: hosted + version: "2.4.10" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + sodium: + dependency: transitive + description: + name: sodium + sha256: d9830a388e37c82891888e64cfd4c6764fa3ac716bed80ac6eab89ee42c3cd76 + url: "https://pub.dev" + source: hosted + version: "2.3.1+1" + sodium_libs: + dependency: transitive + description: + name: sodium_libs + sha256: aa764acd6ccc6113e119c2d99471aeeb4637a9a501639549b297d3a143ff49b3 + url: "https://pub.dev" + source: hosted + version: "2.2.1+6" source_span: dependency: transitive description: @@ -144,6 +563,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -168,6 +595,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: @@ -184,6 +619,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + tuple: + dependency: "direct main" + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_math: dependency: transitive description: @@ -200,6 +659,38 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + url: "https://pub.dev" + source: hosted + version: "5.14.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: - dart: ">=3.7.0-0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.29.0" diff --git a/mobile/packages/configuration/pubspec.yaml b/mobile/packages/configuration/pubspec.yaml index 4883fdb8dd..df08596507 100644 --- a/mobile/packages/configuration/pubspec.yaml +++ b/mobile/packages/configuration/pubspec.yaml @@ -1,6 +1,7 @@ name: configuration description: A Flutter package for shared configuration across ente apps version: 1.0.0 +publish_to: none environment: sdk: ">=3.0.0 <4.0.0" @@ -9,6 +10,20 @@ environment: dependencies: flutter: sdk: flutter + bip39: ^1.0.6 + ente_base: + path: ../../packages/base + ente_crypto_dart: + git: + url: https://github.com/ente-io/ente_crypto_dart.git + ente_events: + path: ../../packages/events + ente_logging: + path: ../../packages/logging + flutter_secure_storage: ^9.0.0 + path_provider: ^2.1.5 + shared_preferences: ^2.5.3 + tuple: ^2.0.2 dev_dependencies: flutter_test: diff --git a/mobile/packages/events/analysis_options.yaml b/mobile/packages/events/analysis_options.yaml new file mode 100644 index 0000000000..609eb5d8aa --- /dev/null +++ b/mobile/packages/events/analysis_options.yaml @@ -0,0 +1,10 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + - always_declare_return_types + - always_put_required_named_parameters_first + - avoid_print + - prefer_const_constructors + - prefer_const_literals_to_create_immutables + - use_key_in_widget_constructors diff --git a/mobile/packages/events/lib/ente_events.dart b/mobile/packages/events/lib/ente_events.dart new file mode 100644 index 0000000000..023d166e49 --- /dev/null +++ b/mobile/packages/events/lib/ente_events.dart @@ -0,0 +1,13 @@ +library ente_events; + +// Export the event bus +export 'event_bus.dart'; + +// Export all event models +export 'models/event.dart'; +export 'models/signed_in_event.dart'; +export 'models/signed_out_event.dart'; +export 'models/trigger_logout_event.dart'; +export 'models/user_details_changed_event.dart'; +export 'models/endpoint_updated_event.dart'; +export 'models/collections_updated_event.dart'; diff --git a/mobile/packages/events/lib/event_bus.dart b/mobile/packages/events/lib/event_bus.dart new file mode 100644 index 0000000000..162a107439 --- /dev/null +++ b/mobile/packages/events/lib/event_bus.dart @@ -0,0 +1,5 @@ +import 'package:event_bus/event_bus.dart'; + +class Bus { + static final EventBus instance = EventBus(); +} diff --git a/mobile/packages/events/lib/models/collections_updated_event.dart b/mobile/packages/events/lib/models/collections_updated_event.dart new file mode 100644 index 0000000000..5fb0736a26 --- /dev/null +++ b/mobile/packages/events/lib/models/collections_updated_event.dart @@ -0,0 +1,3 @@ +import 'event.dart'; + +class CollectionsUpdatedEvent extends Event {} diff --git a/mobile/packages/events/lib/models/endpoint_updated_event.dart b/mobile/packages/events/lib/models/endpoint_updated_event.dart new file mode 100644 index 0000000000..33c88d420d --- /dev/null +++ b/mobile/packages/events/lib/models/endpoint_updated_event.dart @@ -0,0 +1,3 @@ +import 'event.dart'; + +class EndpointUpdatedEvent extends Event {} diff --git a/mobile/packages/events/lib/models/event.dart b/mobile/packages/events/lib/models/event.dart new file mode 100644 index 0000000000..61a42e7107 --- /dev/null +++ b/mobile/packages/events/lib/models/event.dart @@ -0,0 +1 @@ +class Event {} diff --git a/mobile/packages/events/lib/models/signed_in_event.dart b/mobile/packages/events/lib/models/signed_in_event.dart new file mode 100644 index 0000000000..15ae9cedc9 --- /dev/null +++ b/mobile/packages/events/lib/models/signed_in_event.dart @@ -0,0 +1,3 @@ +import 'event.dart'; + +class SignedInEvent extends Event {} diff --git a/mobile/packages/events/lib/models/signed_out_event.dart b/mobile/packages/events/lib/models/signed_out_event.dart new file mode 100644 index 0000000000..84e7dc7822 --- /dev/null +++ b/mobile/packages/events/lib/models/signed_out_event.dart @@ -0,0 +1,3 @@ +import 'event.dart'; + +class SignedOutEvent extends Event {} diff --git a/mobile/packages/events/lib/models/trigger_logout_event.dart b/mobile/packages/events/lib/models/trigger_logout_event.dart new file mode 100644 index 0000000000..48b068ffce --- /dev/null +++ b/mobile/packages/events/lib/models/trigger_logout_event.dart @@ -0,0 +1,3 @@ +import 'event.dart'; + +class TriggerLogoutEvent extends Event {} diff --git a/mobile/packages/events/lib/models/user_details_changed_event.dart b/mobile/packages/events/lib/models/user_details_changed_event.dart new file mode 100644 index 0000000000..9cd352a2ac --- /dev/null +++ b/mobile/packages/events/lib/models/user_details_changed_event.dart @@ -0,0 +1,3 @@ +import 'event.dart'; + +class UserDetailsChangedEvent extends Event {} diff --git a/mobile/packages/events/pubspec.lock b/mobile/packages/events/pubspec.lock new file mode 100644 index 0000000000..2ee3344a78 --- /dev/null +++ b/mobile/packages/events/pubspec.lock @@ -0,0 +1,213 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + event_bus: + dependency: "direct main" + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/mobile/packages/events/pubspec.yaml b/mobile/packages/events/pubspec.yaml new file mode 100644 index 0000000000..dc21a6ca95 --- /dev/null +++ b/mobile/packages/events/pubspec.yaml @@ -0,0 +1,19 @@ +name: ente_events +description: A Flutter package for event management using event_bus, extracted from the surprise app +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + event_bus: ^2.0.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/mobile/packages/logging/analysis_options.yaml b/mobile/packages/logging/analysis_options.yaml new file mode 100644 index 0000000000..609eb5d8aa --- /dev/null +++ b/mobile/packages/logging/analysis_options.yaml @@ -0,0 +1,10 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + - always_declare_return_types + - always_put_required_named_parameters_first + - avoid_print + - prefer_const_constructors + - prefer_const_literals_to_create_immutables + - use_key_in_widget_constructors diff --git a/mobile/packages/logging/lib/logging.dart b/mobile/packages/logging/lib/logging.dart new file mode 100644 index 0000000000..9e31883c01 --- /dev/null +++ b/mobile/packages/logging/lib/logging.dart @@ -0,0 +1,3 @@ +export 'src/super_logging.dart'; +export 'src/tunneled_transport.dart'; +export 'package:logging/logging.dart'; diff --git a/mobile/packages/logging/lib/src/super_logging.dart b/mobile/packages/logging/lib/src/super_logging.dart new file mode 100644 index 0000000000..f78b48d758 --- /dev/null +++ b/mobile/packages/logging/lib/src/super_logging.dart @@ -0,0 +1,402 @@ +import 'dart:async'; +import 'dart:collection'; +import 'dart:core'; +import 'dart:io'; + +import 'tunneled_transport.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; +import 'package:logging/logging.dart' as log; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:uuid/uuid.dart'; + +/// Type definition for functions that may return a value or Future +typedef FutureOrVoidCallback = dynamic Function(); + +extension SuperString on String { + Iterable chunked(int chunkSize) sync* { + var start = 0; + + while (true) { + final stop = start + chunkSize; + if (stop > length) break; + yield substring(start, stop); + start = stop; + } + + if (start < length) { + yield substring(start); + } + } +} + +extension SuperLogRecord on log.LogRecord { + String toPrettyString([String? extraLines]) { + final header = "[$loggerName] [$level] [$time]"; + + var msg = "$header $message"; + + if (error != null) { + msg += "\n⤷ type: ${error.runtimeType}\n⤷ error: $error"; + } + if (stackTrace != null) { + msg += "\n⤷ trace: $stackTrace"; + } + + for (var line in extraLines?.split('\n') ?? []) { + msg += '\n$header $line'; + } + + return msg; + } +} + +class LogConfig { + /// The DSN for a Sentry app. + /// This can be obtained from the Sentry apps's "settings > Client Keys (DSN)" page. + /// + /// Only logs containing errors are sent to sentry. + /// Errors can be caught using a try-catch block, like so: + /// + /// ``` + /// final logger = Logger("main"); + /// + /// try { + /// // do something dangerous here + /// } catch(e, trace) { + /// logger.info("Huston, we have a problem", e, trace); + /// } + /// ``` + /// + /// If this is [null], Sentry logger is completely disabled (default). + String? sentryDsn; + + String? tunnel; + + /// A built-in retry mechanism for sending errors to sentry. + /// + /// This parameter defines the time to wait for, before retrying. + Duration sentryRetryDelay; + + /// Path of the directory where log files will be stored. + /// + /// If this is [null], file logging is completely disabled (default). + /// + /// If this is an empty string (['']), + /// then a 'logs' directory will be created in [getTemporaryDirectory()]. + /// + /// A non-empty string will be treated as an explicit path to a directory. + /// + /// The chosen directory can be accessed using [SuperLogging.logFile.parent]. + String? logDirPath; + + /// The maximum number of log files inside [logDirPath]. + /// + /// One log file is created per day. + /// Older log files are deleted automatically. + int maxLogFiles; + + /// Whether to enable super logging features in debug mode. + /// + /// Sentry and file logging are typically not needed in debug mode, + /// where a complete logcat is available. + bool enableInDebugMode; + + /// If provided, super logging will invoke this function, and + /// any uncaught errors during its execution will be reported. + /// + /// Works by using [FlutterError.onError] and [runZoned]. + FutureOrVoidCallback? body; + + /// The date format for storing log files. + /// + /// `DateFormat('y-M-d')` by default. + DateFormat? dateFmt; + + String prefix; + + LogConfig({ + this.sentryDsn, + this.tunnel, + this.sentryRetryDelay = const Duration(seconds: 30), + this.logDirPath, + this.maxLogFiles = 10, + this.enableInDebugMode = false, + this.body, + this.dateFmt, + this.prefix = "", + }) { + dateFmt ??= DateFormat("y-M-d"); + } +} + +class SuperLogging { + /// The logger for SuperLogging + static final $ = log.Logger('ente_logging'); + + /// The current super logging configuration + static late LogConfig config; + + static late SharedPreferences _preferences; + + static const keyShouldReportErrors = "should_report_errors"; + + static const keyAnonymousUserID = "anonymous_user_id"; + + static Future main([LogConfig? appConfig]) async { + appConfig ??= LogConfig(); + SuperLogging.config = appConfig; + + WidgetsFlutterBinding.ensureInitialized(); + _preferences = await SharedPreferences.getInstance(); + + appVersion ??= await getAppVersion(); + + final enable = appConfig.enableInDebugMode || kReleaseMode; + sentryIsEnabled = + enable && appConfig.sentryDsn != null && shouldReportErrors(); + fileIsEnabled = enable && appConfig.logDirPath != null; + + if (fileIsEnabled) { + await setupLogDir(); + } + if (sentryIsEnabled) { + setupSentry().ignore(); + } + + log.Logger.root.level = log.Level.ALL; + log.Logger.root.onRecord.listen(onLogRecord); + + if (!enable) { + $.info("detected debug mode; sentry & file logging disabled."); + } + if (fileIsEnabled) { + $.info("log file for today: $logFile with prefix ${appConfig.prefix}"); + } + if (sentryIsEnabled) { + $.info("sentry uploader started"); + } + + if (appConfig.body == null) return; + + if (enable && sentryIsEnabled) { + await SentryFlutter.init( + (options) { + options.dsn = appConfig!.sentryDsn; + options.httpClient = http.Client(); + if (appConfig.tunnel != null) { + options.transport = + TunneledTransport(Uri.parse(appConfig.tunnel!), options); + } + }, + appRunner: () => appConfig!.body!(), + ); + } else { + await appConfig.body!(); + } + } + + static void setUserID(String userID) async { + if (config.sentryDsn != null) { + Sentry.configureScope((scope) => scope.setUser(SentryUser(id: userID))); + $.info("setting sentry user ID to: $userID"); + } + } + + static Future _sendErrorToSentry( + Object error, + StackTrace? stack, + ) async { + try { + await Sentry.captureException( + error, + stackTrace: stack, + ); + } catch (e) { + $.info('Sending report to sentry.io failed: $e'); + $.info('Original error: $error'); + } + } + + static String _lastExtraLines = ''; + + static Future onLogRecord(log.LogRecord rec) async { + // log misc info if it changed + String? extraLines = "app version: '$appVersion'\n"; + if (extraLines != _lastExtraLines) { + _lastExtraLines = extraLines; + } else { + extraLines = null; + } + + final str = "${config.prefix} ${rec.toPrettyString(extraLines)}"; + + // write to stdout + printLog(str); + + // push to log queue + if (fileIsEnabled) { + fileQueueEntries.add('$str\n'); + if (fileQueueEntries.length == 1) { + flushQueue(); + } + } + + // add error to sentry queue + if (sentryIsEnabled && rec.error != null) { + _sendErrorToSentry(rec.error!, null).ignore(); + } + } + + static final Queue fileQueueEntries = Queue(); + static bool isFlushing = false; + + static void flushQueue() async { + if (isFlushing || logFile == null) { + return; + } + isFlushing = true; + final entry = fileQueueEntries.removeFirst(); + await logFile!.writeAsString(entry, mode: FileMode.append, flush: true); + isFlushing = false; + if (fileQueueEntries.isNotEmpty) { + flushQueue(); + } + } + + // Logs on must be chunked or they get truncated otherwise + // See https://github.com/flutter/flutter/issues/22665 + static var logChunkSize = 800; + + static void printLog(String text) { + text.chunked(logChunkSize).forEach(debugPrint); + } + + /// A queue to be consumed by [setupSentry]. + static final sentryQueueControl = StreamController(); + + /// Whether sentry logging is currently enabled or not. + static bool sentryIsEnabled = false; + + static Future setupSentry() async { + $.info("Setting up sentry"); + SuperLogging.setUserID(await _getOrCreateAnonymousUserID()); + await for (final error in sentryQueueControl.stream.asBroadcastStream()) { + try { + await Sentry.captureException( + error, + ); + } catch (e) { + $.fine( + "sentry upload failed; will retry after ${config.sentryRetryDelay}", + ); + doSentryRetry(error); + } + } + } + + static void doSentryRetry(Error error) async { + await Future.delayed(config.sentryRetryDelay); + sentryQueueControl.add(error); + } + + static bool shouldReportErrors() { + if (_preferences.containsKey(keyShouldReportErrors)) { + return _preferences.getBool(keyShouldReportErrors)!; + } else { + return kDebugMode; + } + } + + static Future setShouldReportErrors(bool value) { + return _preferences.setBool(keyShouldReportErrors, value); + } + + static Future _getOrCreateAnonymousUserID() async { + if (!_preferences.containsKey(keyAnonymousUserID)) { + //ignore: prefer_const_constructors + await _preferences.setString(keyAnonymousUserID, Uuid().v4()); + } + return _preferences.getString(keyAnonymousUserID)!; + } + + /// The log file currently in use. + static File? logFile; + + /// Whether file logging is currently enabled or not. + static bool fileIsEnabled = false; + + static Future setupLogDir() async { + var dirPath = config.logDirPath; + + // choose [logDir] + if (dirPath == null || dirPath.isEmpty) { + final root = await getExternalStorageDirectory(); + dirPath = '${root!.path}/logs'; + } + + // create [logDir] + final dir = Directory(dirPath); + await dir.create(recursive: true); + + final files = []; + final dates = {}; + + // collect all log files with valid names + await for (final file in dir.list()) { + try { + final date = config.dateFmt!.parse(basename(file.path)); + dates[file as File] = date; + files.add(file); + } on Exception catch (_) {} + } + final nowTime = DateTime.now(); + + // delete old log files, if [maxLogFiles] is exceeded. + if (files.length > config.maxLogFiles) { + // sort files based on ascending order of date (older first) + files.sort( + (a, b) => (dates[a] ?? nowTime).compareTo((dates[b] ?? nowTime)), + ); + + final extra = files.length - config.maxLogFiles; + final toDelete = files.sublist(0, extra); + + for (final file in toDelete) { + try { + $.fine( + "deleting log file ${file.path}", + ); + await file.delete(); + } on Exception catch (_) {} + } + } + + logFile = File("$dirPath/${config.dateFmt!.format(DateTime.now())}.txt"); + } + + /// Current app version, obtained from package_info plugin. + /// + /// See: [getAppVersion] + static String? appVersion; + + static Future getAppVersion() async { + final pkgInfo = await PackageInfo.fromPlatform(); + return "${pkgInfo.version}+${pkgInfo.buildNumber}"; + } + + // disable sentry on f-droid. We need to make it opt-in preference + static Future isFDroidBuild() async { + if (!Platform.isAndroid) { + return false; + } + final pkgName = (await PackageInfo.fromPlatform()).packageName; + return pkgName.endsWith("fdroid"); + } +} diff --git a/mobile/packages/logging/lib/src/tunneled_transport.dart b/mobile/packages/logging/lib/src/tunneled_transport.dart new file mode 100644 index 0000000000..f9ffd5ad12 --- /dev/null +++ b/mobile/packages/logging/lib/src/tunneled_transport.dart @@ -0,0 +1,140 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:sentry/sentry.dart'; + +/// A transport is in charge of sending the event to the Sentry server. +class TunneledTransport implements Transport { + final Uri _tunnel; + final SentryOptions _options; + + final Dsn? _dsn; + + _CredentialBuilder? _credentialBuilder; + + final Map _headers; + + factory TunneledTransport(Uri tunnel, SentryOptions options) { + return TunneledTransport._(tunnel, options); + } + + TunneledTransport._(this._tunnel, this._options) + : _dsn = _options.dsn != null ? Dsn.parse(_options.dsn!) : null, + _headers = _buildHeaders( + _options.platformChecker.isWeb, + _options.sentryClientName, + ) { + _credentialBuilder = _CredentialBuilder( + _dsn, + _options.sentryClientName, + // ignore: invalid_use_of_internal_member + _options.clock, + ); + } + + @override + Future send(SentryEnvelope envelope) async { + final streamedRequest = await _createStreamedRequest(envelope); + final response = await _options.httpClient + .send(streamedRequest) + .then(Response.fromStream); + + if (response.statusCode != 200) { + // body guard to not log the error as it has performance impact to allocate + // the body String. + if (_options.debug) { + _options.logger( + SentryLevel.error, + 'API returned an error, statusCode = ${response.statusCode}, ' + 'body = ${response.body}', + ); + } + return const SentryId.empty(); + } else { + _options.logger( + SentryLevel.debug, + 'Envelope ${envelope.header.eventId ?? "--"} was sent successfully.', + ); + } + + final eventId = json.decode(response.body)['id']; + if (eventId == null) { + return null; + } + return SentryId.fromId(eventId); + } + + Future _createStreamedRequest( + SentryEnvelope envelope, + ) async { + final streamedRequest = StreamedRequest('POST', _tunnel); + envelope + .envelopeStream(_options) + .listen(streamedRequest.sink.add) + .onDone(streamedRequest.sink.close); + + streamedRequest.headers.addAll(_credentialBuilder!.configure(_headers)); + + return streamedRequest; + } +} + +class _CredentialBuilder { + final String _authHeader; + + final ClockProvider _clock; + + int get timestamp => _clock().millisecondsSinceEpoch; + + _CredentialBuilder._(String authHeader, ClockProvider clock) + : _authHeader = authHeader, + _clock = clock; + + factory _CredentialBuilder( + Dsn? dsn, + String sdkIdentifier, + ClockProvider clock, + ) { + final authHeader = _buildAuthHeader( + publicKey: dsn?.publicKey, + secretKey: dsn?.secretKey, + sdkIdentifier: sdkIdentifier, + ); + + return _CredentialBuilder._(authHeader, clock); + } + + static String _buildAuthHeader({ + String? publicKey, + String? secretKey, + String? sdkIdentifier, + }) { + var header = 'Sentry sentry_version=7, sentry_client=$sdkIdentifier, ' + 'sentry_key=$publicKey'; + + if (secretKey != null) { + header += ', sentry_secret=$secretKey'; + } + + return header; + } + + Map configure(Map headers) { + return headers + ..addAll( + { + 'X-Sentry-Auth': '$_authHeader, sentry_timestamp=$timestamp', + }, + ); + } +} + +Map _buildHeaders(bool isWeb, String sdkIdentifier) { + final headers = {'Content-Type': 'application/x-sentry-envelope'}; + // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why + // for web it use browser user agent + if (!isWeb) { + headers['User-Agent'] = sdkIdentifier; + } + return headers; +} diff --git a/mobile/packages/logging/pubspec.lock b/mobile/packages/logging/pubspec.lock new file mode 100644 index 0000000000..4dd0697bf7 --- /dev/null +++ b/mobile/packages/logging/pubspec.lock @@ -0,0 +1,474 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + logging: + dependency: "direct main" + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sentry: + dependency: "direct main" + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: "direct main" + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" + url: "https://pub.dev" + source: hosted + version: "2.4.10" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + url: "https://pub.dev" + source: hosted + version: "5.14.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/mobile/packages/logging/pubspec.yaml b/mobile/packages/logging/pubspec.yaml new file mode 100644 index 0000000000..5dbf6ea650 --- /dev/null +++ b/mobile/packages/logging/pubspec.yaml @@ -0,0 +1,28 @@ +name: ente_logging +description: A Flutter package for logging functionality across ente apps +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + http: ^1.4.0 + intl: ^0.20.2 + logging: ^1.3.0 + package_info_plus: ^8.3.0 + path: ^1.9.0 + path_provider: ^2.1.5 + sentry: ^8.14.2 + sentry_flutter: ^8.14.2 + shared_preferences: ^2.5.3 + uuid: ^4.5.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: