diff --git a/native/backup/restore-siwe-backup.react.js b/native/backup/restore-siwe-backup.react.js index f4e10d58f..294f3d35c 100644 --- a/native/backup/restore-siwe-backup.react.js +++ b/native/backup/restore-siwe-backup.react.js @@ -1,83 +1,83 @@ // @flow import * as React from 'react'; import { Alert } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { type SIWEResult } from 'lib/types/siwe-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { SignSIWEBackupMessageForRestore } from '../account/registration/siwe-backup-message-creation.react.js'; import { commCoreModule } from '../native-modules.js'; import { type RootNavigationProp } from '../navigation/root-navigator.react.js'; import { type NavigationRoute } from '../navigation/route-names.js'; import { persistConfig } from '../redux/persist.js'; import { useStyles } from '../themes/colors.js'; export type RestoreSIWEBackupParams = { +backupID: string, +siweNonce: string, +siweStatement: string, +siweIssuedAt: string, }; type Props = { +navigation: RootNavigationProp<'RestoreSIWEBackup'>, +route: NavigationRoute<'RestoreSIWEBackup'>, }; function RestoreSIWEBackup(props: Props): React.Node { const styles = useStyles(unboundStyles); const { goBack } = props.navigation; const { route } = props; const { params: { backupID, siweStatement, siweIssuedAt, siweNonce }, } = route; const onSuccessfulWalletSignature = React.useCallback( (result: SIWEResult) => { void (async () => { const { signature } = result; let message = 'success'; try { - await commCoreModule.restoreSIWEBackup( + await commCoreModule.restoreBackup( signature, - backupID, persistConfig.version.toString(), + backupID, ); } catch (e) { message = `Backup restore error: ${String( getMessageForException(e), )}`; console.error(message); } Alert.alert('Restore protocol result', message); goBack(); })(); }, [goBack, backupID], ); return ( ); } const safeAreaEdges = ['top']; const unboundStyles = { container: { flex: 1, backgroundColor: 'panelBackground', justifyContent: 'space-between', }, }; export default RestoreSIWEBackup; diff --git a/native/backup/use-client-backup.js b/native/backup/use-client-backup.js index ec11b398b..c66bbd52f 100644 --- a/native/backup/use-client-backup.js +++ b/native/backup/use-client-backup.js @@ -1,141 +1,88 @@ // @flow import * as React from 'react'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; import { accountHasPassword } from 'lib/shared/account-utils.js'; import { latestBackupInfoResponseValidator, - siweBackupDataValidator, type LatestBackupInfo, - type SIWEBackupData, } from 'lib/types/backup-types.js'; import type { SIWEBackupSecrets } from 'lib/types/siwe-types.js'; import { assertWithValidator } from 'lib/utils/validation-utils.js'; import { fetchNativeKeychainCredentials } from '../account/native-credentials.js'; import { commCoreModule } from '../native-modules.js'; -import { persistConfig } from '../redux/persist.js'; import { useSelector } from '../redux/redux-utils.js'; type ClientBackup = { +uploadBackupProtocol: () => Promise, - +restorePasswordUserBackupProtocol: () => Promise, - +retrieveLatestSIWEBackupData: () => Promise, +retrieveLatestBackupInfo: () => Promise, }; async function getBackupSecret(): Promise { const nativeCredentials = await fetchNativeKeychainCredentials(); if (!nativeCredentials) { throw new Error('Native credentials are missing'); } return nativeCredentials.password; } async function getSIWEBackupSecrets(): Promise { const siweBackupSecrets = await commCoreModule.getSIWEBackupSecrets(); if (!siweBackupSecrets) { throw new Error('SIWE backup message and its signature are missing'); } return siweBackupSecrets; } function useClientBackup(): ClientBackup { const currentUserID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); const currentUserInfo = useSelector(state => state.currentUserInfo); const loggedIn = useSelector(isLoggedIn); 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...'); if (accountHasPassword(currentUserInfo)) { const backupSecret = await getBackupSecret(); await commCoreModule.createNewBackup(backupSecret); } else { const { message, signature } = await getSIWEBackupSecrets(); await commCoreModule.createNewSIWEBackup(signature, message); } console.info('Backup uploaded.'); }, [currentUserID, loggedIn, currentUserInfo]); - const retrieveLatestSIWEBackupData = React.useCallback(async () => { - if (!loggedIn || !currentUserID) { - throw new Error('Attempt to restore backup for not logged in user.'); - } - - if (accountHasPassword(currentUserInfo)) { - throw new Error( - 'Attempt to retrieve siwe backup data for password user.', - ); - } - - const serializedBackupData = - await commCoreModule.retrieveLatestSIWEBackupData(); - - return assertWithValidator( - JSON.parse(serializedBackupData), - siweBackupDataValidator, - ); - }, [currentUserID, currentUserInfo, loggedIn]); - const retrieveLatestBackupInfo = React.useCallback(async () => { if (!loggedIn || !currentUserID || !currentUserInfo?.username) { throw new Error('Attempt to restore backup for not logged in user.'); } const userIdentitifer = currentUserInfo?.username; const response = await commCoreModule.retrieveLatestBackupInfo(userIdentitifer); return assertWithValidator( JSON.parse(response), latestBackupInfoResponseValidator, ); }, [currentUserID, currentUserInfo, loggedIn]); - const restorePasswordUserBackupProtocol = React.useCallback(async () => { - if (!accountHasPassword(currentUserInfo)) { - throw new Error( - 'Attempt to restore from password for non-password user.', - ); - } - - const [latestBackupInfo, backupSecret] = await Promise.all([ - retrieveLatestBackupInfo(), - getBackupSecret(), - ]); - - console.info('Start restoring backup...'); - await commCoreModule.restoreBackup( - backupSecret, - persistConfig.version.toString(), - latestBackupInfo.backupID, - ); - console.info('Backup restored.'); - }, [currentUserInfo, retrieveLatestBackupInfo]); - return React.useMemo( () => ({ uploadBackupProtocol, - restorePasswordUserBackupProtocol, - retrieveLatestSIWEBackupData, retrieveLatestBackupInfo, }), - [ - restorePasswordUserBackupProtocol, - retrieveLatestBackupInfo, - retrieveLatestSIWEBackupData, - uploadBackupProtocol, - ], + [retrieveLatestBackupInfo, uploadBackupProtocol], ); } export { getBackupSecret, useClientBackup }; diff --git a/native/profile/backup-menu.react.js b/native/profile/backup-menu.react.js index 54dbc4a30..df0025c33 100644 --- a/native/profile/backup-menu.react.js +++ b/native/profile/backup-menu.react.js @@ -1,221 +1,234 @@ // @flow import { useNavigation } from '@react-navigation/native'; import * as React from 'react'; import { Switch, Text, View } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; import { accountHasPassword } from 'lib/shared/account-utils.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import type { ProfileNavigationProp } from './profile.react.js'; -import { useClientBackup } from '../backup/use-client-backup.js'; +import { + getBackupSecret, + useClientBackup, +} from '../backup/use-client-backup.js'; import Button from '../components/button.react.js'; +import { commCoreModule } from '../native-modules.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { RestoreSIWEBackupRouteName } from '../navigation/route-names.js'; import { setLocalSettingsActionType } from '../redux/action-types.js'; +import { persistConfig } from '../redux/persist.js'; import { useSelector } from '../redux/redux-utils.js'; import { useColors, useStyles } from '../themes/colors.js'; import Alert from '../utils/alert.js'; type Props = { +navigation: ProfileNavigationProp<'BackupMenu'>, +route: NavigationRoute<'BackupMenu'>, }; // eslint-disable-next-line no-unused-vars function BackupMenu(props: Props): React.Node { const styles = useStyles(unboundStyles); const dispatch = useDispatch(); const colors = useColors(); const currentUserInfo = useSelector(state => state.currentUserInfo); const navigation = useNavigation(); const isBackupEnabled = useSelector( state => state.localSettings.isBackupEnabled, ); - const { - uploadBackupProtocol, - restorePasswordUserBackupProtocol, - retrieveLatestSIWEBackupData, - retrieveLatestBackupInfo, - } = useClientBackup(); + const { uploadBackupProtocol, retrieveLatestBackupInfo } = useClientBackup(); const uploadBackup = React.useCallback(async () => { let message = 'Success'; try { await uploadBackupProtocol(); } catch (e) { message = `Backup upload error: ${String(getMessageForException(e))}`; console.error(message); } Alert.alert('Upload protocol result', message); }, [uploadBackupProtocol]); const testRestoreForPasswordUser = React.useCallback(async () => { let message = 'success'; try { - await restorePasswordUserBackupProtocol(); + const [latestBackupInfo, backupSecret] = await Promise.all([ + retrieveLatestBackupInfo(), + getBackupSecret(), + ]); + await commCoreModule.restoreBackup( + backupSecret, + persistConfig.version.toString(), + latestBackupInfo.backupID, + ); + console.info('Backup restored.'); } catch (e) { message = `Backup restore error: ${String(getMessageForException(e))}`; console.error(message); } Alert.alert('Restore protocol result', message); - }, [restorePasswordUserBackupProtocol]); + }, [retrieveLatestBackupInfo]); const testLatestBackupInfo = React.useCallback(async () => { let message; try { const backupInfo = await retrieveLatestBackupInfo(); const { backupID, userID } = backupInfo; message = `Success!\n` + `Backup ID: ${backupID},\n` + `userID: ${userID},\n` + `userID check: ${currentUserInfo?.id === userID ? 'true' : 'false'}`; } catch (e) { message = `Latest backup info error: ${String( getMessageForException(e), )}`; console.error(message); } Alert.alert('Latest backup info result', message); }, [currentUserInfo?.id, retrieveLatestBackupInfo]); const testRestoreForSIWEUser = React.useCallback(async () => { let message = 'success'; try { - const siweBackupData = await retrieveLatestSIWEBackupData(); + const backupInfo = await retrieveLatestBackupInfo(); + const { siweBackupData, backupID } = backupInfo; + + if (!siweBackupData) { + throw new Error('Missing SIWE message for Wallet user backup'); + } const { - backupID, siweBackupMsgNonce, siweBackupMsgIssuedAt, siweBackupMsgStatement, } = siweBackupData; navigation.navigate<'RestoreSIWEBackup'>({ name: RestoreSIWEBackupRouteName, params: { backupID, siweNonce: siweBackupMsgNonce, siweStatement: siweBackupMsgStatement, siweIssuedAt: siweBackupMsgIssuedAt, }, }); } catch (e) { message = `Backup restore error: ${String(getMessageForException(e))}`; console.error(message); } - }, [navigation, retrieveLatestSIWEBackupData]); + }, [navigation, retrieveLatestBackupInfo]); const onBackupToggled = React.useCallback( (value: boolean) => { dispatch({ type: setLocalSettingsActionType, payload: { isBackupEnabled: value }, }); }, [dispatch], ); const onPressRestoreButton = accountHasPassword(currentUserInfo) ? testRestoreForPasswordUser : testRestoreForSIWEUser; return ( SETTINGS Toggle automatic backup ACTIONS ); } const unboundStyles = { scrollViewContentContainer: { paddingTop: 24, }, scrollView: { backgroundColor: 'panelBackground', }, section: { backgroundColor: 'panelForeground', borderBottomWidth: 1, borderColor: 'panelForegroundBorder', borderTopWidth: 1, marginBottom: 24, marginVertical: 2, }, header: { color: 'panelBackgroundLabel', fontSize: 12, fontWeight: '400', paddingBottom: 3, paddingHorizontal: 24, }, submenuButton: { flexDirection: 'row', paddingHorizontal: 24, paddingVertical: 10, alignItems: 'center', }, submenuText: { color: 'panelForegroundLabel', flex: 1, fontSize: 16, }, row: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 24, paddingVertical: 14, }, }; export default BackupMenu;