diff --git a/lib/types/backup-types.js b/lib/types/backup-types.js --- a/lib/types/backup-types.js +++ b/lib/types/backup-types.js @@ -32,3 +32,20 @@ userID: t.String, siweBackupData: t.maybe(siweBackupDataValidator), }); + +// This type should match `UserKeys` in +// `native/native_rust_library/src/backup.rs` +export type UserKeys = { + +backupDataKey: string, + +backupLogDataKey: string, + +pickleKey: string, + +pickledAccount: string, +}; +export const userKeysResponseValidator: TInterface = tShape( + { + backupDataKey: t.String, + backupLogDataKey: t.String, + pickleKey: t.String, + pickledAccount: t.String, + }, +); diff --git a/native/backup/restore-siwe-backup.react.js b/native/backup/restore-siwe-backup.react.js --- a/native/backup/restore-siwe-backup.react.js +++ b/native/backup/restore-siwe-backup.react.js @@ -4,8 +4,10 @@ import { Alert } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; +import { userKeysResponseValidator } from 'lib/types/backup-types.js'; import { type SIWEResult } from 'lib/types/siwe-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; +import { assertWithValidator } from 'lib/utils/validation-utils.js'; import { SignSIWEBackupMessageForRestore } from '../account/registration/siwe-backup-message-creation.react.js'; import { commCoreModule } from '../native-modules.js'; @@ -19,6 +21,7 @@ +siweNonce: string, +siweStatement: string, +siweIssuedAt: string, + +userIdentifier: string, }; type Props = { @@ -31,21 +34,34 @@ const { goBack } = props.navigation; const { route } = props; const { - params: { siweStatement, siweIssuedAt, siweNonce }, + params: { + backupID, + siweStatement, + siweIssuedAt, + siweNonce, + userIdentifier, + }, } = route; const onSuccessfulWalletSignature = React.useCallback( (result: SIWEResult) => { void (async () => { - // eslint-disable-next-line no-unused-vars const { signature } = result; let message = 'success'; try { - //TODO add backup keys + const userKeysResponse = commCoreModule.getBackupUserKeys( + userIdentifier, + signature, + backupID, + ); + const userKeys = assertWithValidator( + userKeysResponse, + userKeysResponseValidator, + ); await commCoreModule.restoreBackupData( - '', - '', - '', + backupID, + userKeys.backupDataKey, + userKeys.backupLogDataKey, persistConfig.version.toString(), ); } catch (e) { @@ -58,7 +74,7 @@ goBack(); })(); }, - [goBack], + [backupID, goBack, userIdentifier], ); return ( 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 @@ -16,7 +16,10 @@ type ClientBackup = { +createFullBackup: () => Promise, +createUserKeysBackup: () => Promise, - +retrieveLatestBackupInfo: () => Promise, + +retrieveLatestBackupInfo: () => Promise<{ + +latestBackupInfo: LatestBackupInfo, + +userIdentifier: string, + }>, }; function useClientBackup(): ClientBackup { @@ -49,15 +52,16 @@ if (!loggedIn || !currentUserID || !currentUserInfo?.username) { throw new Error('Attempt to restore backup for not logged in user.'); } - const userIdentitifer = currentUserInfo?.username; + const userIdentifier = currentUserInfo?.username; const response = - await commCoreModule.retrieveLatestBackupInfo(userIdentitifer); + await commCoreModule.retrieveLatestBackupInfo(userIdentifier); - return assertWithValidator( + const latestBackupInfo = assertWithValidator( JSON.parse(response), latestBackupInfoResponseValidator, ); + return { latestBackupInfo, userIdentifier }; }, [currentUserID, currentUserInfo, loggedIn]); return React.useMemo( diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -225,6 +225,11 @@ jsi::String backupLogDataKey, jsi::String maxVersion) override; virtual jsi::Value getQRAuthBackupData(jsi::Runtime &rt) override; + virtual jsi::Value getBackupUserKeys( + jsi::Runtime &rt, + jsi::String userIdentifier, + jsi::String backupSecret, + jsi::String backupID) override; virtual jsi::Value retrieveLatestBackupInfo( jsi::Runtime &rt, jsi::String userIdentifier) override; diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -2724,6 +2724,26 @@ }); } +jsi::Value CommCoreModule::getBackupUserKeys( + jsi::Runtime &rt, + jsi::String userIdentifier, + jsi::String backupSecret, + jsi::String backupID) { + std::string userIdentifierStr = userIdentifier.utf8(rt); + std::string backupSecretStr = backupSecret.utf8(rt); + std::string backupIDStr = backupID.utf8(rt); + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + auto currentID = RustPromiseManager::instance.addPromise( + {promise, this->jsInvoker_, innerRt}); + ::getBackupUserKeys( + rust::string(userIdentifierStr), + rust::string(backupSecretStr), + rust::string(backupIDStr), + currentID); + }); +} + jsi::Value CommCoreModule::retrieveLatestBackupInfo( jsi::Runtime &rt, jsi::String userIdentifier) { diff --git a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp --- a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp +++ b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp @@ -196,6 +196,9 @@ static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_retrieveLatestBackupInfo(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->retrieveLatestBackupInfo(rt, args[0].asString(rt)); } +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getBackupUserKeys(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->getBackupUserKeys(rt, args[0].asString(rt), args[1].asString(rt), args[2].asString(rt)); +} static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_setSIWEBackupSecrets(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->setSIWEBackupSecrets(rt, args[0].asObject(rt)); } @@ -303,6 +306,7 @@ methodMap_["restoreBackupData"] = MethodMetadata {4, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_restoreBackupData}; methodMap_["getQRAuthBackupData"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getQRAuthBackupData}; methodMap_["retrieveLatestBackupInfo"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_retrieveLatestBackupInfo}; + methodMap_["getBackupUserKeys"] = MethodMetadata {3, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getBackupUserKeys}; methodMap_["setSIWEBackupSecrets"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_setSIWEBackupSecrets}; methodMap_["getSIWEBackupSecrets"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getSIWEBackupSecrets}; methodMap_["getAllInboundP2PMessages"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllInboundP2PMessages}; diff --git a/native/cpp/CommonCpp/_generated/commJSI.h b/native/cpp/CommonCpp/_generated/commJSI.h --- a/native/cpp/CommonCpp/_generated/commJSI.h +++ b/native/cpp/CommonCpp/_generated/commJSI.h @@ -79,6 +79,7 @@ virtual jsi::Value restoreBackupData(jsi::Runtime &rt, jsi::String backupID, jsi::String backupDataKey, jsi::String backupLogDataKey, jsi::String maxVersion) = 0; virtual jsi::Value getQRAuthBackupData(jsi::Runtime &rt) = 0; virtual jsi::Value retrieveLatestBackupInfo(jsi::Runtime &rt, jsi::String userIdentifier) = 0; + virtual jsi::Value getBackupUserKeys(jsi::Runtime &rt, jsi::String userIdentifier, jsi::String backupSecret, jsi::String backupID) = 0; virtual jsi::Value setSIWEBackupSecrets(jsi::Runtime &rt, jsi::Object siweBackupSecrets) = 0; virtual jsi::Value getSIWEBackupSecrets(jsi::Runtime &rt) = 0; virtual jsi::Value getAllInboundP2PMessages(jsi::Runtime &rt) = 0; @@ -587,6 +588,14 @@ return bridging::callFromJs( rt, &T::retrieveLatestBackupInfo, jsInvoker_, instance_, std::move(userIdentifier)); } + jsi::Value getBackupUserKeys(jsi::Runtime &rt, jsi::String userIdentifier, jsi::String backupSecret, jsi::String backupID) override { + static_assert( + bridging::getParameterCount(&T::getBackupUserKeys) == 4, + "Expected getBackupUserKeys(...) to have 4 parameters"); + + return bridging::callFromJs( + rt, &T::getBackupUserKeys, jsInvoker_, instance_, std::move(userIdentifier), std::move(backupSecret), std::move(backupID)); + } jsi::Value setSIWEBackupSecrets(jsi::Runtime &rt, jsi::Object siweBackupSecrets) override { static_assert( bridging::getParameterCount(&T::setSIWEBackupSecrets) == 2, 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 @@ -138,15 +138,17 @@ }); } - pub fn retrieve_backup_keys( + pub fn get_backup_user_keys( + user_identifier: String, backup_secret: String, backup_id: String, promise_id: u32, ) { RUNTIME.spawn(async move { - let result = download_backup_keys(backup_id, backup_secret) - .await - .map_err(|err| err.to_string()); + let result = + download_backup_keys(user_identifier, backup_secret, backup_id) + .await + .map_err(|err| err.to_string()); let result = match result { Ok(result) => result, @@ -342,16 +344,13 @@ } async fn download_backup_keys( - backup_id: String, + user_identifier: String, backup_secret: String, + backup_id: String, ) -> Result> { let backup_client = BackupClient::new(BACKUP_SOCKET_ADDR)?; - let user_identity = get_user_identity_from_secure_store()?; - let backup_descriptor = BackupDescriptor::BackupID { - backup_id: backup_id.clone(), - user_identity: user_identity.clone(), - }; + let backup_descriptor = BackupDescriptor::Latest { user_identifier }; let mut encrypted_user_keys = backup_client .download_backup_data(&backup_descriptor, RequestedData::UserKeys) @@ -450,7 +449,9 @@ /// The reasoning behind this decision is that the backed-up Olm account /// is primarily used for signing an update to the device list. For these /// operations only the identity signing key is necessary. +// This struct should match `UserKeys` in `lib/types/backup-types.js` #[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] struct UserKeys { backup_data_key: String, backup_log_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 @@ -455,8 +455,9 @@ promise_id: u32, ); - #[cxx_name = "retrieveBackupKeys"] - fn retrieve_backup_keys( + #[cxx_name = "getBackupUserKeys"] + fn get_backup_user_keys( + user_identifier: String, backup_secret: String, backup_id: String, promise_id: u32, 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 @@ -6,8 +6,10 @@ import { ScrollView } from 'react-native-gesture-handler'; import { accountHasPassword } from 'lib/shared/account-utils.js'; +import { userKeysResponseValidator } from 'lib/types/backup-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; +import { assertWithValidator } from 'lib/utils/validation-utils.js'; import type { ProfileNavigationProp } from './profile.react.js'; import { useClientBackup } from '../backup/use-client-backup.js'; @@ -69,16 +71,21 @@ const testRestoreForPasswordUser = React.useCallback(async () => { let message = 'success'; try { - // eslint-disable-next-line no-unused-vars - const [latestBackupInfo, backupSecret] = await Promise.all([ - retrieveLatestBackupInfo(), - getBackupSecret(), - ]); - //TODO add backup keys + const [{ latestBackupInfo, userIdentifier }, backupSecret] = + await Promise.all([retrieveLatestBackupInfo(), getBackupSecret()]); + const userKeysResponse = await commCoreModule.getBackupUserKeys( + userIdentifier, + backupSecret, + latestBackupInfo.backupID, + ); + const userKeys = assertWithValidator( + JSON.parse(userKeysResponse), + userKeysResponseValidator, + ); await commCoreModule.restoreBackupData( latestBackupInfo.backupID, - '', - '', + userKeys.backupDataKey, + userKeys.backupLogDataKey, persistConfig.version.toString(), ); console.info('Backup restored.'); @@ -92,8 +99,8 @@ const testLatestBackupInfo = React.useCallback(async () => { let message; try { - const backupInfo = await retrieveLatestBackupInfo(); - const { backupID, userID } = backupInfo; + const { latestBackupInfo } = await retrieveLatestBackupInfo(); + const { backupID, userID } = latestBackupInfo; message = `Success!\n` + `Backup ID: ${backupID},\n` + @@ -111,8 +118,9 @@ const testRestoreForSIWEUser = React.useCallback(async () => { let message = 'success'; try { - const backupInfo = await retrieveLatestBackupInfo(); - const { siweBackupData, backupID } = backupInfo; + const { latestBackupInfo, userIdentifier } = + await retrieveLatestBackupInfo(); + const { siweBackupData, backupID } = latestBackupInfo; if (!siweBackupData) { throw new Error('Missing SIWE message for Wallet user backup'); @@ -131,6 +139,7 @@ siweNonce: siweBackupMsgNonce, siweStatement: siweBackupMsgStatement, siweIssuedAt: siweBackupMsgIssuedAt, + userIdentifier, }, }); } catch (e) { diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -167,6 +167,11 @@ ) => Promise; +getQRAuthBackupData: () => Promise; +retrieveLatestBackupInfo: (userIdentifier: string) => Promise; + +getBackupUserKeys: ( + userIdentifier: string, + backupSecret: string, + backupID: string, + ) => Promise; +setSIWEBackupSecrets: (siweBackupSecrets: Object) => Promise; +getSIWEBackupSecrets: () => Promise; +getAllInboundP2PMessages: () => Promise>;