create backup zip file

This commit is contained in:
otsmr 2025-06-17 18:51:52 +02:00
parent 4536e82e1b
commit fcc2c9fb9e
13 changed files with 119 additions and 22 deletions

View file

@ -1,4 +1,7 @@
class SecureStorageKeys { class SecureStorageKeys {
static const String signalIdentity = "signal_identity"; static const String signalIdentity = "signal_identity";
static const String signalSignedPreKey = "signed_pre_key_store"; 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";
} }

View file

@ -10,7 +10,6 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
// this constructor is required so that the main database can create an instance // this constructor is required so that the main database can create an instance
// of this object. // of this object.
SignalDao(super.db); SignalDao(super.db);
Future deleteAllByContactId(int contactId) async { Future deleteAllByContactId(int contactId) async {
await (delete(signalContactPreKeys) await (delete(signalContactPreKeys)
..where((t) => t.contactId.equals(contactId))) ..where((t) => t.contactId.equals(contactId)))

View file

@ -129,4 +129,13 @@ class TwonlyDatabase extends _$TwonlyDatabase {
notifyUpdates({TableUpdate.onTable(messages, kind: UpdateKind.update)}); notifyUpdates({TableUpdate.onTable(messages, kind: UpdateKind.update)});
notifyUpdates({TableUpdate.onTable(contacts, 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();
}
} }

View file

@ -12,6 +12,7 @@ import 'package:mutex/mutex.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/app.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/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/client_to_server.pbserver.dart';
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
@ -329,7 +330,8 @@ class ApiService {
Future<bool> tryAuthenticateWithToken(int userId) async { Future<bool> tryAuthenticateWithToken(int userId) async {
final storage = FlutterSecureStorage(); final storage = FlutterSecureStorage();
String? apiAuthToken = await storage.read(key: "api_auth_token"); String? apiAuthToken =
await storage.read(key: SecureStorageKeys.apiAuthToken);
if (apiAuthToken != null) { if (apiAuthToken != null) {
final authenticate = Handshake_Authenticate() final authenticate = Handshake_Authenticate()
@ -414,7 +416,8 @@ class ApiService {
String apiAuthTokenB64 = base64Encode(apiAuthToken); String apiAuthTokenB64 = base64Encode(apiAuthToken);
final storage = FlutterSecureStorage(); final storage = FlutterSecureStorage();
await storage.write(key: "api_auth_token", value: apiAuthTokenB64); await storage.write(
key: SecureStorageKeys.apiAuthToken, value: apiAuthTokenB64);
await tryAuthenticateWithToken(userData.userId); await tryAuthenticateWithToken(userData.userId);
} }

View file

@ -13,6 +13,7 @@ import 'package:mutex/mutex.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:twonly/globals.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/media_uploads_table.dart';
import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
@ -562,8 +563,8 @@ Future<bool> handleMediaUpload(MediaUpload media) async {
final uploadRequestBytes = uploadRequest.writeToBuffer(); final uploadRequestBytes = uploadRequest.writeToBuffer();
final storage = FlutterSecureStorage(); String? apiAuthToken =
String? apiAuthToken = await storage.read(key: "api_auth_token"); await FlutterSecureStorage().read(key: SecureStorageKeys.apiAuthToken);
if (apiAuthToken == null) { if (apiAuthToken == null) {
Log.error("api auth token not defined."); Log.error("api auth token not defined.");
return false; return false;

View file

@ -1,8 +1,79 @@
import 'dart:convert'; 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/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hashlib/hashlib.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'; 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<void> createZipArchive(String zipFilePath, List<File> 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 { Future enableTwonlySafe(String password) async {
final user = await getUser(); final user = await getUser();
if (user == null) return; if (user == null) return;
@ -16,6 +87,7 @@ Future enableTwonlySafe(String password) async {
return user; return user;
}); });
startTwonlySafeBackup(); startTwonlySafeBackup();
performTwonlySafeBackup();
} }
Future disableTwonlySafe() async { Future disableTwonlySafe() async {

View file

@ -3,6 +3,7 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/app.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/database/twonly_database.dart';
import 'package:twonly/src/services/notification.service.dart'; import 'package:twonly/src/services/notification.service.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
@ -16,7 +17,7 @@ Future initFCMAfterAuthenticated() async {
final storage = FlutterSecureStorage(); final storage = FlutterSecureStorage();
String? storedToken = await storage.read(key: "google_fcm"); String? storedToken = await storage.read(key: SecureStorageKeys.googleFcm);
try { try {
final fcmToken = await FirebaseMessaging.instance.getToken(); final fcmToken = await FirebaseMessaging.instance.getToken();
@ -27,12 +28,12 @@ Future initFCMAfterAuthenticated() async {
if (storedToken == null || fcmToken != storedToken) { if (storedToken == null || fcmToken != storedToken) {
await apiService.updateFCMToken(fcmToken); 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 { FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) async {
await apiService.updateFCMToken(fcmToken); await apiService.updateFCMToken(fcmToken);
await storage.write(key: "google_fcm", value: fcmToken); await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
}).onError((err) { }).onError((err) {
Log.error("could not listen on token refresh"); Log.error("could not listen on token refresh");
}); });

View file

@ -4,6 +4,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mutex/mutex.dart'; import 'package:mutex/mutex.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:provider/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/model/json/userdata.dart';
import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/providers/connection.provider.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
@ -17,8 +18,8 @@ Future<bool> isUserCreated() async {
} }
Future<UserData?> getUser() async { Future<UserData?> getUser() async {
final storage = FlutterSecureStorage(); String? userJson =
String? userJson = await storage.read(key: "userData"); await FlutterSecureStorage().read(key: SecureStorageKeys.userData);
if (userJson == null) { if (userJson == null) {
return null; return null;
} }
@ -51,8 +52,8 @@ Future<UserData?> updateUserdata(Function(UserData userData) updateUser) async {
final user = await getUser(); final user = await getUser();
if (user == null) return null; if (user == null) return null;
UserData updated = updateUser(user); UserData updated = updateUser(user);
final storage = FlutterSecureStorage(); FlutterSecureStorage()
storage.write(key: "userData", value: jsonEncode(updated)); .write(key: SecureStorageKeys.userData, value: jsonEncode(updated));
return user; return user;
}); });
} }
@ -62,7 +63,6 @@ Future<bool> deleteLocalUserData() async {
if (appDir.existsSync()) { if (appDir.existsSync()) {
appDir.deleteSync(recursive: true); appDir.deleteSync(recursive: true);
} }
final storage = FlutterSecureStorage(); await FlutterSecureStorage().deleteAll();
await storage.deleteAll();
return true; return true;
} }

View file

@ -4,6 +4,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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/services/signal/identity.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/components/alert_dialog.dart';
@ -32,8 +33,6 @@ class _RegisterViewState extends State<RegisterView> {
_isTryingToRegister = true; _isTryingToRegister = true;
}); });
final storage = FlutterSecureStorage();
await createIfNotExistsSignalIdentity(); await createIfNotExistsSignalIdentity();
int userId = 0; int userId = 0;
@ -66,7 +65,10 @@ class _RegisterViewState extends State<RegisterView> {
subscriptionPlan: "Preview", subscriptionPlan: "Preview",
isDemoUser: isDemoAccount, isDemoUser: isDemoAccount,
); );
storage.write(key: "userData", value: jsonEncode(userData));
FlutterSecureStorage()
.write(key: SecureStorageKeys.userData, value: jsonEncode(userData));
if (!isDemoAccount) { if (!isDemoAccount) {
await apiService.authenticate(); await apiService.authenticate();
} else { } else {

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:twonly/src/services/backup.identitiy.service.dart'; import 'package:twonly/src/services/backup.identitiy.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.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'; import 'package:twonly/src/views/settings/backup/twonly_safe_backup.view.dart';
class BackupView extends StatefulWidget { class BackupView extends StatefulWidget {
@ -49,7 +50,11 @@ class _BackupViewState extends State<BackupView> {
autoBackupEnabled: _twonlyIdBackupEnabled, autoBackupEnabled: _twonlyIdBackupEnabled,
onTap: () async { onTap: () async {
if (_twonlyIdBackupEnabled) { if (_twonlyIdBackupEnabled) {
bool disable = await showAlertDialog(context, "Are you sure?",
"Without an backup, you can not restore your user account.");
if (disable) {
await disableTwonlySafe(); await disableTwonlySafe();
}
} else { } else {
await Navigator.push(context, await Navigator.push(context,
MaterialPageRoute(builder: (context) { MaterialPageRoute(builder: (context) {
@ -110,7 +115,7 @@ class BackupOption extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: (autoBackupEnabled) ? null : onTap,
child: Card( child: Card(
margin: EdgeInsets.all(8.0), margin: EdgeInsets.all(8.0),
child: Padding( child: Padding(

View file

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:twonly/globals.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/views/components/alert_dialog.dart';
import 'package:twonly/src/services/fcm.service.dart'; import 'package:twonly/src/services/fcm.service.dart';
import 'package:twonly/src/services/notification.service.dart'; import 'package:twonly/src/services/notification.service.dart';
@ -26,8 +27,8 @@ class NotificationView extends StatelessWidget {
subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc), subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc),
onTap: () async { onTap: () async {
await initFCMAfterAuthenticated(); await initFCMAfterAuthenticated();
final storage = FlutterSecureStorage(); String? storedToken = await FlutterSecureStorage()
String? storedToken = await storage.read(key: "google_fcm"); .read(key: SecureStorageKeys.googleFcm);
await setupNotificationWithUsers(force: true); await setupNotificationWithUsers(force: true);
if (!context.mounted) return; if (!context.mounted) return;

View file

@ -34,7 +34,7 @@ packages:
source: hosted source: hosted
version: "7.4.5" version: "7.4.5"
archive: archive:
dependency: transitive dependency: "direct main"
description: description:
name: archive name: archive
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"

View file

@ -67,6 +67,7 @@ dependencies:
tutorial_coach_mark: ^1.3.0 tutorial_coach_mark: ^1.3.0
background_downloader: ^9.2.2 background_downloader: ^9.2.2
hashlib: ^2.0.0 hashlib: ^2.0.0
archive: ^4.0.7
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: