diff --git a/native/backup/use-client-backup.js b/native/backup/use-client-backup.js --- a/native/backup/use-client-backup.js +++ b/native/backup/use-client-backup.js @@ -1,25 +1,17 @@ // @flow -import _isEqual from 'lodash/fp/isEqual.js'; import * as React from 'react'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; -import type { UserStore } from 'lib/types/user-types.js'; import { fetchNativeKeychainCredentials } from '../account/native-credentials.js'; import { commCoreModule } from '../native-modules.js'; import { useSelector } from '../redux/redux-utils.js'; import { getContentSigningKey } from '../utils/crypto-utils.js'; -type UserData = { - +userStore: UserStore, -}; - type ClientBackup = { +uploadBackupProtocol: () => Promise, - +restoreBackupProtocol: ( - expectedUserData: UserData, - ) => Promise<{ +dataIntegritySuccess: boolean }>, + +restoreBackupProtocol: () => Promise, }; async function getBackupSecret(): Promise { @@ -37,11 +29,10 @@ ); const loggedIn = useSelector(isLoggedIn); - const uploadBackupProtocol = React.useCallback(async () => { - if (!loggedIn || !currentUserID) { - throw new Error('Attempt to upload backup for not logged in user.'); + const setMockCommServicesAuthMetadata = React.useCallback(async () => { + if (!currentUserID) { + return; } - console.info('Start uploading backup...'); const ed25519 = await getContentSigningKey(); await commCoreModule.setCommServicesAuthMetadata( @@ -49,27 +40,35 @@ ed25519, accessToken ? accessToken : '', ); + }, [accessToken, currentUserID]); + + const uploadBackupProtocol = React.useCallback(async () => { + if (!loggedIn || !currentUserID) { + throw new Error('Attempt to upload backup for not logged in user.'); + } + console.info('Start uploading backup...'); + + await setMockCommServicesAuthMetadata(); const backupSecret = await getBackupSecret(); await commCoreModule.createNewBackup(backupSecret); console.info('Backup uploaded.'); - }, [accessToken, currentUserID, loggedIn]); + }, [currentUserID, loggedIn, setMockCommServicesAuthMetadata]); - const restoreBackupProtocol = React.useCallback( - async (expectedUserData: UserData) => { - if (!loggedIn || !currentUserID) { - throw new Error('Attempt to restore backup for not logged in user.'); - } + const restoreBackupProtocol = React.useCallback(async () => { + if (!loggedIn || !currentUserID) { + throw new Error('Attempt to restore backup for not logged in user.'); + } - const backupSecret = await getBackupSecret(); - const restoreResultStr = await commCoreModule.restoreBackup(backupSecret); - const { userData }: { userData: UserData } = JSON.parse(restoreResultStr); + console.info('Start restoring backup...'); - return { dataIntegritySuccess: !!_isEqual(userData, expectedUserData) }; - }, - [currentUserID, loggedIn], - ); + await setMockCommServicesAuthMetadata(); + const backupSecret = await getBackupSecret(); + await commCoreModule.restoreBackup(backupSecret); + + console.info('Backup restored.'); + }, [currentUserID, loggedIn, setMockCommServicesAuthMetadata]); return { uploadBackupProtocol, restoreBackupProtocol }; } diff --git a/native/cpp/CommonCpp/NativeModules/PersistentStorageUtilities/BackupOperationsUtilities/BackupOperationsExecutor.h b/native/cpp/CommonCpp/NativeModules/PersistentStorageUtilities/BackupOperationsUtilities/BackupOperationsExecutor.h --- a/native/cpp/CommonCpp/NativeModules/PersistentStorageUtilities/BackupOperationsUtilities/BackupOperationsExecutor.h +++ b/native/cpp/CommonCpp/NativeModules/PersistentStorageUtilities/BackupOperationsUtilities/BackupOperationsExecutor.h @@ -9,7 +9,8 @@ static void createMainCompaction(std::string backupID, size_t futureID); static void restoreFromMainCompaction( std::string mainCompactionPath, - std::string mainCompactionEncryptionKey); + std::string mainCompactionEncryptionKey, + size_t futureID); static void restoreFromBackupLog(const std::vector &backupLog); }; } // namespace comm diff --git a/native/cpp/CommonCpp/NativeModules/PersistentStorageUtilities/BackupOperationsUtilities/BackupOperationsExecutor.cpp b/native/cpp/CommonCpp/NativeModules/PersistentStorageUtilities/BackupOperationsUtilities/BackupOperationsExecutor.cpp --- a/native/cpp/CommonCpp/NativeModules/PersistentStorageUtilities/BackupOperationsUtilities/BackupOperationsExecutor.cpp +++ b/native/cpp/CommonCpp/NativeModules/PersistentStorageUtilities/BackupOperationsUtilities/BackupOperationsExecutor.cpp @@ -2,6 +2,7 @@ #include "DatabaseManager.h" #include "GlobalDBSingleton.h" #include "Logger.h" +#include "RustPromiseManager.h" #include "WorkerThread.h" #include "lib.rs.h" @@ -24,17 +25,18 @@ void BackupOperationsExecutor::restoreFromMainCompaction( std::string mainCompactionPath, - std::string mainCompactionEncryptionKey) { - taskType job = [mainCompactionPath, mainCompactionEncryptionKey]() { + std::string mainCompactionEncryptionKey, + size_t futureID) { + taskType job = [mainCompactionPath, mainCompactionEncryptionKey, futureID]() { try { DatabaseManager::getQueryExecutor().restoreFromMainCompaction( mainCompactionPath, mainCompactionEncryptionKey); + ::resolveUnitFuture(futureID); } catch (const std::exception &e) { - // TODO: Inform Rust networking about failure - // of restoration from main compaction. + std::string errorDetails = std::string(e.what()); Logger::log( - "Restore from main compaction failed. Details: " + - std::string(e.what())); + "Restore from main compaction failed. Details: " + errorDetails); + ::rejectFuture(futureID, errorDetails); } }; GlobalDBSingleton::instance.scheduleOrRunCancellable(job); diff --git a/native/native_rust_library/RustBackupExecutor.h b/native/native_rust_library/RustBackupExecutor.h --- a/native/native_rust_library/RustBackupExecutor.h +++ b/native/native_rust_library/RustBackupExecutor.h @@ -11,8 +11,9 @@ rust::String getBackupUserKeysFilePath(rust::Str backupID); void createMainCompaction(rust::Str backupID, size_t futureID); void restoreFromMainCompaction( - rust::String mainCompactionPath, - rust::String mainCompactionEncryptionKey); + rust::Str mainCompactionPath, + rust::Str mainCompactionEncryptionKey, + size_t futureID); void restoreFromBackupLog(rust::Vec backupLog); } // namespace comm diff --git a/native/native_rust_library/RustBackupExecutor.cpp b/native/native_rust_library/RustBackupExecutor.cpp --- a/native/native_rust_library/RustBackupExecutor.cpp +++ b/native/native_rust_library/RustBackupExecutor.cpp @@ -32,11 +32,13 @@ } void restoreFromMainCompaction( - rust::String mainCompactionPath, - rust::String mainCompactionEncryptionKey) { + rust::Str mainCompactionPath, + rust::Str mainCompactionEncryptionKey, + size_t futureID) { BackupOperationsExecutor::restoreFromMainCompaction( std::string(mainCompactionPath), - std::string(mainCompactionEncryptionKey)); + std::string(mainCompactionEncryptionKey), + futureID); } void restoreFromBackupLog(rust::Vec backupLog) { diff --git a/native/native_rust_library/src/backup.rs b/native/native_rust_library/src/backup.rs --- a/native/native_rust_library/src/backup.rs +++ b/native/native_rust_library/src/backup.rs @@ -5,10 +5,11 @@ use crate::argon2_tools::{compute_backup_key, compute_backup_key_str}; use crate::constants::{aes, secure_store}; use crate::ffi::{ - create_main_compaction, get_backup_user_keys_file_path, secure_store_get, + create_main_compaction, get_backup_directory_path, + get_backup_user_keys_file_path, restore_from_main_compaction, + secure_store_get, void_callback, }; use crate::future_manager; -use crate::handle_string_result_as_callback; use crate::BACKUP_SOCKET_ADDR; use crate::RUNTIME; use backup_client::{ @@ -16,8 +17,8 @@ UserIdentity, }; use serde::{Deserialize, Serialize}; -use serde_json::json; use std::error::Error; +use std::path::PathBuf; pub mod ffi { use super::*; @@ -62,10 +63,33 @@ }); } - pub fn restore_backup_sync(backup_secret: String, promise_id: u32) { + pub fn restore_backup(backup_secret: String, promise_id: u32) { RUNTIME.spawn(async move { - let result = restore_backup(backup_secret).await; - handle_string_result_as_callback(result, promise_id); + let result = download_backup(backup_secret) + .await + .map_err(|err| err.to_string()); + + let result = match result { + Ok(result) => result, + Err(error) => { + void_callback(error, promise_id); + return; + } + }; + + let (future_id, future) = future_manager::new_future::<()>().await; + restore_from_main_compaction( + &result.backup_restoration_path.to_string_lossy(), + &result.backup_data_key, + future_id, + ); + + if let Err(error) = future.await { + void_callback(error, promise_id); + return; + } + + void_callback(String::new(), promise_id); }); } } @@ -95,9 +119,9 @@ Ok(()) } -pub async fn restore_backup( +async fn download_backup( backup_secret: String, -) -> Result> { +) -> Result> { let backup_client = BackupClient::new(BACKUP_SOCKET_ADDR)?; let user_identity = get_user_identity_from_secure_store()?; @@ -127,25 +151,20 @@ user_identity: user_identity.clone(), }; - let mut encrypted_user_data = backup_client + let encrypted_user_data = backup_client .download_backup_data(&backup_data_descriptor, RequestedData::UserData) .await?; - let user_data = decrypt( - &mut user_keys.backup_data_key.as_bytes().to_vec(), - &mut encrypted_user_data, - )?; - - let user_data: serde_json::Value = serde_json::from_slice(&user_data)?; - - Ok( - json!({ - "userData": user_data, - "pickleKey": user_keys.pickle_key, - "pickledAccount": user_keys.pickled_account, - }) - .to_string(), - ) + let backup_restoration_path = + PathBuf::from(get_backup_directory_path()?).join("restore_compaction"); + + tokio::fs::write(&backup_restoration_path, encrypted_user_data).await?; + + Ok(CompactionDownloadResult { + backup_id, + backup_restoration_path, + backup_data_key: user_keys.backup_data_key, + }) } fn get_user_identity_from_secure_store() -> Result @@ -157,6 +176,12 @@ }) } +struct CompactionDownloadResult { + backup_id: String, + backup_restoration_path: PathBuf, + backup_data_key: String, +} + #[derive(Debug, Serialize, Deserialize)] struct UserKeys { backup_data_key: String, diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs --- a/native/native_rust_library/src/lib.rs +++ b/native/native_rust_library/src/lib.rs @@ -319,7 +319,7 @@ ); #[cxx_name = "restoreBackup"] - fn restore_backup_sync(backup_secret: String, promise_id: u32); + fn restore_backup(backup_secret: String, promise_id: u32); } // Secure store @@ -362,12 +362,12 @@ #[cxx_name = "createMainCompaction"] fn create_main_compaction(backup_id: &str, future_id: usize); - #[allow(unused)] #[cxx_name = "restoreFromMainCompaction"] fn restore_from_main_compaction( - main_compaction_path: String, - main_compaction_encryption_key: String, - ) -> Result<()>; + main_compaction_path: &str, + main_compaction_encryption_key: &str, + future_id: usize, + ); #[allow(unused)] #[cxx_name = "restoreFromBackupLog"] diff --git a/native/profile/backup-menu.react.js b/native/profile/backup-menu.react.js --- a/native/profile/backup-menu.react.js +++ b/native/profile/backup-menu.react.js @@ -5,7 +5,6 @@ import { ScrollView } from 'react-native-gesture-handler'; import { getMessageForException } from 'lib/utils/errors.js'; -import { entries } from 'lib/utils/objects.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import type { ProfileNavigationProp } from './profile.react.js'; @@ -26,7 +25,6 @@ const dispatch = useDispatch(); const colors = useColors(); - const userStore = useSelector(state => state.userStore); const isBackupEnabled = useSelector( state => state.localSettings.isBackupEnabled, ); @@ -45,18 +43,15 @@ }, [uploadBackupProtocol]); const testRestore = React.useCallback(async () => { - let message; + let message = 'success'; try { - const result = await restoreBackupProtocol({ userStore }); - message = entries(result) - .map(([key, value]) => `${key}: ${String(value)}`) - .join('\n'); + await restoreBackupProtocol(); } catch (e) { message = `Backup restore error: ${String(getMessageForException(e))}`; console.error(message); } Alert.alert('Restore protocol result', message); - }, [restoreBackupProtocol, userStore]); + }, [restoreBackupProtocol]); const onBackupToggled = React.useCallback( (value: boolean) => { diff --git a/shared/backup_client/Cargo.lock b/shared/backup_client/Cargo.lock --- a/shared/backup_client/Cargo.lock +++ b/shared/backup_client/Cargo.lock @@ -159,6 +159,7 @@ "reqwest", "serde_json", "sha2", + "tokio", "tokio-tungstenite", "url", ]