diff --git a/lib/src/constants/secure_storage_keys.dart b/lib/src/constants/secure_storage_keys.dart index 035f397..e0c6431 100644 --- a/lib/src/constants/secure_storage_keys.dart +++ b/lib/src/constants/secure_storage_keys.dart @@ -1,4 +1,7 @@ class SecureStorageKeys { static const String signalIdentity = "signal_identity"; static const String signalSignedPreKey = "signed_pre_key_store"; + static const String apiAuthToken = "api_auth_token"; + static const String googleFcm = "google_fcm"; + static const String userData = "userData"; } diff --git a/lib/src/database/daos/signal_dao.dart b/lib/src/database/daos/signal_dao.dart index e5b10eb..8a9a2e8 100644 --- a/lib/src/database/daos/signal_dao.dart +++ b/lib/src/database/daos/signal_dao.dart @@ -10,7 +10,6 @@ class SignalDao extends DatabaseAccessor with _$SignalDaoMixin { // this constructor is required so that the main database can create an instance // of this object. SignalDao(super.db); - Future deleteAllByContactId(int contactId) async { await (delete(signalContactPreKeys) ..where((t) => t.contactId.equals(contactId))) diff --git a/lib/src/database/twonly_database.dart b/lib/src/database/twonly_database.dart index f008d01..af91655 100644 --- a/lib/src/database/twonly_database.dart +++ b/lib/src/database/twonly_database.dart @@ -129,4 +129,13 @@ class TwonlyDatabase extends _$TwonlyDatabase { notifyUpdates({TableUpdate.onTable(messages, kind: UpdateKind.update)}); notifyUpdates({TableUpdate.onTable(contacts, kind: UpdateKind.update)}); } + + Future deleteDataForTwonlySafe() async { + await delete(messages).go(); + await delete(messageRetransmissions).go(); + await delete(mediaDownloads).go(); + await delete(mediaUploads).go(); + await delete(signalContactPreKeys).go(); + await delete(signalContactSignedPreKeys).go(); + } } diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index a64480d..f43da76 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -12,6 +12,7 @@ import 'package:mutex/mutex.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/app.dart'; +import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserver.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; @@ -329,7 +330,8 @@ class ApiService { Future tryAuthenticateWithToken(int userId) async { final storage = FlutterSecureStorage(); - String? apiAuthToken = await storage.read(key: "api_auth_token"); + String? apiAuthToken = + await storage.read(key: SecureStorageKeys.apiAuthToken); if (apiAuthToken != null) { final authenticate = Handshake_Authenticate() @@ -414,7 +416,8 @@ class ApiService { String apiAuthTokenB64 = base64Encode(apiAuthToken); final storage = FlutterSecureStorage(); - await storage.write(key: "api_auth_token", value: apiAuthTokenB64); + await storage.write( + key: SecureStorageKeys.apiAuthToken, value: apiAuthTokenB64); await tryAuthenticateWithToken(userData.userId); } diff --git a/lib/src/services/api/media_send.dart b/lib/src/services/api/media_send.dart index eaf1a69..d967eee 100644 --- a/lib/src/services/api/media_send.dart +++ b/lib/src/services/api/media_send.dart @@ -13,6 +13,7 @@ import 'package:mutex/mutex.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/database/tables/media_uploads_table.dart'; import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/database/twonly_database.dart'; @@ -562,8 +563,8 @@ Future handleMediaUpload(MediaUpload media) async { final uploadRequestBytes = uploadRequest.writeToBuffer(); - final storage = FlutterSecureStorage(); - String? apiAuthToken = await storage.read(key: "api_auth_token"); + String? apiAuthToken = + await FlutterSecureStorage().read(key: SecureStorageKeys.apiAuthToken); if (apiAuthToken == null) { Log.error("api auth token not defined."); return false; diff --git a/lib/src/services/backup.identitiy.service.dart b/lib/src/services/backup.identitiy.service.dart index 7c98b45..7d4b994 100644 --- a/lib/src/services/backup.identitiy.service.dart +++ b/lib/src/services/backup.identitiy.service.dart @@ -1,8 +1,79 @@ import 'dart:convert'; +import 'dart:io'; +import 'package:archive/archive_io.dart'; +import 'package:drift_flutter/drift_flutter.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hashlib/hashlib.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:twonly/src/constants/secure_storage_keys.dart'; +import 'package:twonly/src/database/twonly_database.dart'; +import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/storage.dart'; +Future performTwonlySafeBackup() async { + Log.info("Starting new backup creation."); + final baseDir = (await getApplicationSupportDirectory()).path; + + var originalDatabase = File(join(baseDir, "twonly_database.sqlite")); + var backupDir = Directory(join(baseDir, "backup_twonly_safe/")); + if (backupDir.existsSync()) { + await backupDir.delete(recursive: true); + } + await backupDir.create(recursive: true); + + var backupDatabaseFile = + File(join(backupDir.path, "twonly_database.backup.sqlite")); + + // copy database + await originalDatabase.copy(backupDatabaseFile.path); + + final backupDB = TwonlyDatabase( + driftDatabase( + name: "twonly_database.backup", + native: DriftNativeOptions( + databaseDirectory: () async { + return backupDir; + }, + ), + ), + ); + + await backupDB.deleteDataForTwonlySafe(); + + var secureStorageBackup = {}; + final storage = FlutterSecureStorage(); + secureStorageBackup[SecureStorageKeys.signalIdentity] = + await storage.read(key: SecureStorageKeys.signalIdentity); + secureStorageBackup[SecureStorageKeys.signalSignedPreKey] = + await storage.read(key: SecureStorageKeys.signalSignedPreKey); + secureStorageBackup[SecureStorageKeys.userData] = + await storage.read(key: SecureStorageKeys.userData); + + var backupSecureStorage = File(join(backupDir.path, "secure_storage.json")); + + await backupSecureStorage.writeAsString(jsonEncode(secureStorageBackup)); + + Log.info("Backup files created."); + + var twonlySafeBackupZip = File(join(backupDir.path, "twonly_safe.zip")); + + await createZipArchive( + twonlySafeBackupZip.path, [backupSecureStorage, backupDatabaseFile]); + + // await backupDir.delete(recursive: true); +} + +Future createZipArchive(String zipFilePath, List filesToZip) async { + final encoder = ZipFileEncoder(); + encoder.create(zipFilePath); + for (var file in filesToZip) { + await encoder.addFile(file); + } + await encoder.close(); +} + Future enableTwonlySafe(String password) async { final user = await getUser(); if (user == null) return; @@ -16,6 +87,7 @@ Future enableTwonlySafe(String password) async { return user; }); startTwonlySafeBackup(); + performTwonlySafeBackup(); } Future disableTwonlySafe() async { diff --git a/lib/src/services/fcm.service.dart b/lib/src/services/fcm.service.dart index 288c5c2..75f697d 100644 --- a/lib/src/services/fcm.service.dart +++ b/lib/src/services/fcm.service.dart @@ -3,6 +3,7 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/app.dart'; +import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/services/notification.service.dart'; import 'package:twonly/src/utils/log.dart'; @@ -16,7 +17,7 @@ Future initFCMAfterAuthenticated() async { final storage = FlutterSecureStorage(); - String? storedToken = await storage.read(key: "google_fcm"); + String? storedToken = await storage.read(key: SecureStorageKeys.googleFcm); try { final fcmToken = await FirebaseMessaging.instance.getToken(); @@ -27,12 +28,12 @@ Future initFCMAfterAuthenticated() async { if (storedToken == null || fcmToken != storedToken) { await apiService.updateFCMToken(fcmToken); - await storage.write(key: "google_fcm", value: fcmToken); + await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken); } FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) async { await apiService.updateFCMToken(fcmToken); - await storage.write(key: "google_fcm", value: fcmToken); + await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken); }).onError((err) { Log.error("could not listen on token refresh"); }); diff --git a/lib/src/utils/storage.dart b/lib/src/utils/storage.dart index 6665da6..54cf5cd 100644 --- a/lib/src/utils/storage.dart +++ b/lib/src/utils/storage.dart @@ -4,6 +4,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; +import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/utils/log.dart'; @@ -17,8 +18,8 @@ Future isUserCreated() async { } Future getUser() async { - final storage = FlutterSecureStorage(); - String? userJson = await storage.read(key: "userData"); + String? userJson = + await FlutterSecureStorage().read(key: SecureStorageKeys.userData); if (userJson == null) { return null; } @@ -51,8 +52,8 @@ Future updateUserdata(Function(UserData userData) updateUser) async { final user = await getUser(); if (user == null) return null; UserData updated = updateUser(user); - final storage = FlutterSecureStorage(); - storage.write(key: "userData", value: jsonEncode(updated)); + FlutterSecureStorage() + .write(key: SecureStorageKeys.userData, value: jsonEncode(updated)); return user; }); } @@ -62,7 +63,6 @@ Future deleteLocalUserData() async { if (appDir.existsSync()) { appDir.deleteSync(recursive: true); } - final storage = FlutterSecureStorage(); - await storage.deleteAll(); + await FlutterSecureStorage().deleteAll(); return true; } diff --git a/lib/src/views/register.view.dart b/lib/src/views/register.view.dart index 040e753..b2519be 100644 --- a/lib/src/views/register.view.dart +++ b/lib/src/views/register.view.dart @@ -4,6 +4,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:twonly/globals.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/services/signal/identity.signal.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; @@ -32,8 +33,6 @@ class _RegisterViewState extends State { _isTryingToRegister = true; }); - final storage = FlutterSecureStorage(); - await createIfNotExistsSignalIdentity(); int userId = 0; @@ -66,7 +65,10 @@ class _RegisterViewState extends State { subscriptionPlan: "Preview", isDemoUser: isDemoAccount, ); - storage.write(key: "userData", value: jsonEncode(userData)); + + FlutterSecureStorage() + .write(key: SecureStorageKeys.userData, value: jsonEncode(userData)); + if (!isDemoAccount) { await apiService.authenticate(); } else { diff --git a/lib/src/views/settings/backup/backup.view.dart b/lib/src/views/settings/backup/backup.view.dart index ec9eed3..235f390 100644 --- a/lib/src/views/settings/backup/backup.view.dart +++ b/lib/src/views/settings/backup/backup.view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:twonly/src/services/backup.identitiy.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/settings/backup/twonly_safe_backup.view.dart'; class BackupView extends StatefulWidget { @@ -49,7 +50,11 @@ class _BackupViewState extends State { autoBackupEnabled: _twonlyIdBackupEnabled, onTap: () async { if (_twonlyIdBackupEnabled) { - await disableTwonlySafe(); + bool disable = await showAlertDialog(context, "Are you sure?", + "Without an backup, you can not restore your user account."); + if (disable) { + await disableTwonlySafe(); + } } else { await Navigator.push(context, MaterialPageRoute(builder: (context) { @@ -110,7 +115,7 @@ class BackupOption extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: onTap, + onTap: (autoBackupEnabled) ? null : onTap, child: Card( margin: EdgeInsets.all(8.0), child: Padding( diff --git a/lib/src/views/settings/notification.view.dart b/lib/src/views/settings/notification.view.dart index 9ccb7ff..e2f54d1 100644 --- a/lib/src/views/settings/notification.view.dart +++ b/lib/src/views/settings/notification.view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/services/fcm.service.dart'; import 'package:twonly/src/services/notification.service.dart'; @@ -26,8 +27,8 @@ class NotificationView extends StatelessWidget { subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc), onTap: () async { await initFCMAfterAuthenticated(); - final storage = FlutterSecureStorage(); - String? storedToken = await storage.read(key: "google_fcm"); + String? storedToken = await FlutterSecureStorage() + .read(key: SecureStorageKeys.googleFcm); await setupNotificationWithUsers(force: true); if (!context.mounted) return; diff --git a/pubspec.lock b/pubspec.lock index cb53cba..ad2a72b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -34,7 +34,7 @@ packages: source: hosted version: "7.4.5" archive: - dependency: transitive + dependency: "direct main" description: name: archive sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" diff --git a/pubspec.yaml b/pubspec.yaml index c8f24ee..c8dd962 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,6 +67,7 @@ dependencies: tutorial_coach_mark: ^1.3.0 background_downloader: ^9.2.2 hashlib: ^2.0.0 + archive: ^4.0.7 dev_dependencies: flutter_test: