mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 08:18:41 +00:00
139 lines
4.5 KiB
Dart
139 lines
4.5 KiB
Dart
// ignore_for_file: avoid_dynamic_calls
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
|
import 'package:drift/drift.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:http/http.dart' as http;
|
|
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/tables/messages_table.dart';
|
|
import 'package:twonly/src/database/twonly_database.dart';
|
|
import 'package:twonly/src/model/json/userdata.dart';
|
|
import 'package:twonly/src/model/protobuf/backup/backup.pb.dart';
|
|
import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart';
|
|
import 'package:twonly/src/utils/log.dart';
|
|
|
|
Future<void> recoverTwonlySafe(
|
|
String username,
|
|
String password,
|
|
BackupServer? server,
|
|
) async {
|
|
final (backupId, encryptionKey) = await getMasterKey(password, username);
|
|
|
|
final backupServerUrl =
|
|
await getTwonlySafeBackupUrlFromServer(backupId, server);
|
|
|
|
if (backupServerUrl == null) {
|
|
Log.error('Could not create backup url');
|
|
throw Exception('Could not create backup server url');
|
|
}
|
|
|
|
late Uint8List backupData;
|
|
late http.Response response;
|
|
|
|
try {
|
|
response = await http.get(Uri.parse(backupServerUrl), headers: {
|
|
HttpHeaders.acceptHeader: 'application/octet-stream',
|
|
});
|
|
} catch (e) {
|
|
Log.error('Error fetching backup: $e');
|
|
throw Exception('Backup server could not be reached. ($e)');
|
|
}
|
|
|
|
switch (response.statusCode) {
|
|
case 200:
|
|
backupData = response.bodyBytes;
|
|
case 400:
|
|
throw Exception('Bad Request: Validation failed.');
|
|
case 404:
|
|
throw Exception('No backup was found.');
|
|
case 429:
|
|
throw Exception('Too Many Requests: Rate limit reached.');
|
|
default:
|
|
throw Exception('Unexpected error: ${response.statusCode}');
|
|
}
|
|
|
|
return handleBackupData(encryptionKey, backupData);
|
|
}
|
|
|
|
Future<void> handleBackupData(
|
|
Uint8List encryptionKey,
|
|
Uint8List backupData,
|
|
) async {
|
|
final encryptedBackup = TwonlySafeBackupEncrypted.fromBuffer(
|
|
backupData,
|
|
);
|
|
|
|
final secretBox = SecretBox(
|
|
encryptedBackup.cipherText,
|
|
nonce: encryptedBackup.nonce,
|
|
mac: Mac(encryptedBackup.mac),
|
|
);
|
|
|
|
final compressedBytes = await FlutterChacha20.poly1305Aead().decrypt(
|
|
secretBox,
|
|
secretKey: SecretKeyData(encryptionKey),
|
|
);
|
|
|
|
final plaintextBytes = gzip.decode(compressedBytes);
|
|
|
|
final backupContent = TwonlySafeBackupContent.fromBuffer(
|
|
plaintextBytes,
|
|
);
|
|
|
|
final baseDir = (await getApplicationSupportDirectory()).path;
|
|
final originalDatabase = File(join(baseDir, 'twonly_database.sqlite'));
|
|
await originalDatabase.writeAsBytes(backupContent.twonlyDatabase);
|
|
|
|
/// When restoring the last message ID must be increased otherwise
|
|
/// receivers would mark them as duplicates as they where already
|
|
/// send.
|
|
final database = TwonlyDatabase();
|
|
var lastMessageSend = 0;
|
|
int? randomUserId;
|
|
|
|
final contacts = await database.contactsDao.getAllNotBlockedContacts();
|
|
for (final contact in contacts) {
|
|
randomUserId = contact.userId;
|
|
final days = DateTime.now().difference(contact.lastMessageExchange).inDays;
|
|
if (days < lastMessageSend) {
|
|
lastMessageSend = days;
|
|
}
|
|
}
|
|
|
|
if (randomUserId != null) {
|
|
// for each day add 400 message ids
|
|
final dummyMessagesCounter = (lastMessageSend + 1) * 400;
|
|
Log.info(
|
|
'Creating $dummyMessagesCounter dummy messages to increase message counter as last message was $lastMessageSend days ago.');
|
|
for (var i = 0; i < dummyMessagesCounter; i++) {
|
|
await database.messagesDao.insertMessage(
|
|
MessagesCompanion(
|
|
contactId: Value(randomUserId),
|
|
kind: const Value(MessageKind.ack),
|
|
acknowledgeByServer: const Value(true),
|
|
errorWhileSending: const Value(true),
|
|
),
|
|
);
|
|
}
|
|
await database.messagesDao.deleteAllMessagesByContactId(randomUserId);
|
|
}
|
|
|
|
const storage = FlutterSecureStorage();
|
|
|
|
final secureStorage = jsonDecode(backupContent.secureStorageJson);
|
|
|
|
await storage.write(
|
|
key: SecureStorageKeys.signalIdentity,
|
|
value: secureStorage[SecureStorageKeys.signalIdentity] as String);
|
|
await storage.write(
|
|
key: SecureStorageKeys.signalSignedPreKey,
|
|
value: secureStorage[SecureStorageKeys.signalSignedPreKey] as String);
|
|
await storage.write(
|
|
key: SecureStorageKeys.userData,
|
|
value: secureStorage[SecureStorageKeys.userData] as String);
|
|
}
|