diff --git a/native/account/siwe-panel.react.js b/native/account/siwe-panel.react.js index c436eb684..b50b14056 100644 --- a/native/account/siwe-panel.react.js +++ b/native/account/siwe-panel.react.js @@ -1,218 +1,215 @@ // @flow import BottomSheet from '@gorhom/bottom-sheet'; import * as React from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import WebView from 'react-native-webview'; import { getSIWENonce, getSIWENonceActionTypes, siweAuthActionTypes, } from 'lib/actions/siwe-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { SIWEWebViewMessage, SIWEResult } from 'lib/types/siwe-types.js'; import { useServerCall, useDispatchActionPromise, type BindServerCallsParams, } from 'lib/utils/action-utils.js'; -import { commCoreModule } from '../native-modules.js'; import { useSelector } from '../redux/redux-utils.js'; import Alert from '../utils/alert.js'; +import { getContentSigningKey } from '../utils/crypto-utils.js'; import { defaultLandingURLPrefix } from '../utils/url-utils.js'; const commSIWE = `${defaultLandingURLPrefix}/siwe`; const getSIWENonceLoadingStatusSelector = createLoadingStatusSelector( getSIWENonceActionTypes, ); const siweAuthLoadingStatusSelector = createLoadingStatusSelector(siweAuthActionTypes); type Props = { +onClosed: () => mixed, +onClosing: () => mixed, +onSuccessfulWalletSignature: SIWEResult => mixed, +closing: boolean, +setLoading: boolean => mixed, +keyserverCallParamOverride?: $Shape, }; function SIWEPanel(props: Props): React.Node { const dispatchActionPromise = useDispatchActionPromise(); const getSIWENonceCall = useServerCall( getSIWENonce, props.keyserverCallParamOverride, ); const getSIWENonceCallFailed = useSelector( state => getSIWENonceLoadingStatusSelector(state) === 'error', ); const { onClosing } = props; React.useEffect(() => { if (getSIWENonceCallFailed) { Alert.alert( 'Unknown error', 'Uhh... try again?', [{ text: 'OK', onPress: onClosing }], { cancelable: false }, ); } }, [getSIWENonceCallFailed, onClosing]); const siweAuthCallLoading = useSelector( state => siweAuthLoadingStatusSelector(state) === 'loading', ); const [nonce, setNonce] = React.useState(null); const [primaryIdentityPublicKey, setPrimaryIdentityPublicKey] = React.useState(null); React.useEffect(() => { (async () => { dispatchActionPromise( getSIWENonceActionTypes, (async () => { const response = await getSIWENonceCall(); setNonce(response); })(), ); - await commCoreModule.initializeCryptoAccount(); - const { - primaryIdentityPublicKeys: { ed25519 }, - } = await commCoreModule.getUserPublicKey(); + const ed25519 = await getContentSigningKey(); setPrimaryIdentityPublicKey(ed25519); })(); }, [dispatchActionPromise, getSIWENonceCall]); const [isLoading, setLoading] = React.useState(true); const [walletConnectModalHeight, setWalletConnectModalHeight] = React.useState(0); const insets = useSafeAreaInsets(); const bottomInset = insets.bottom; const snapPoints = React.useMemo(() => { if (isLoading) { return [1]; } else if (walletConnectModalHeight) { const baseHeight = bottomInset + walletConnectModalHeight; if (baseHeight < 400) { return [baseHeight + 3]; } else { return [baseHeight - 17]; } } else { return [bottomInset + 435, bottomInset + 600]; } }, [isLoading, walletConnectModalHeight, bottomInset]); const bottomSheetRef = React.useRef(); const snapToIndex = bottomSheetRef.current?.snapToIndex; React.useEffect(() => { // When the snapPoints change, always reset to the first one // Without this, when we close the WalletConnect modal we don't resize snapToIndex?.(0); }, [snapToIndex, snapPoints]); const closeBottomSheet = bottomSheetRef.current?.close; const { closing, onSuccessfulWalletSignature } = props; const handleMessage = React.useCallback( async event => { const data: SIWEWebViewMessage = JSON.parse(event.nativeEvent.data); if (data.type === 'siwe_success') { const { address, message, signature } = data; if (address && signature) { closeBottomSheet?.(); await onSuccessfulWalletSignature({ address, message, signature }); } } else if (data.type === 'siwe_closed') { onClosing(); closeBottomSheet?.(); } else if (data.type === 'walletconnect_modal_update') { const height = data.state === 'open' ? data.height : 0; setWalletConnectModalHeight(height); } }, [onSuccessfulWalletSignature, onClosing, closeBottomSheet], ); const prevClosingRef = React.useRef(); React.useEffect(() => { if (closing && !prevClosingRef.current) { closeBottomSheet?.(); } prevClosingRef.current = closing; }, [closing, closeBottomSheet]); const source = React.useMemo( () => ({ uri: commSIWE, headers: { 'siwe-nonce': nonce, 'siwe-primary-identity-public-key': primaryIdentityPublicKey, }, }), [nonce, primaryIdentityPublicKey], ); const onWebViewLoaded = React.useCallback(() => { setLoading(false); }, []); const walletConnectModalOpen = walletConnectModalHeight !== 0; const backgroundStyle = React.useMemo( () => ({ backgroundColor: walletConnectModalOpen ? '#3396ff' : '#242529', }), [walletConnectModalOpen], ); const bottomSheetHandleIndicatorStyle = React.useMemo( () => ({ backgroundColor: 'white', }), [], ); const { onClosed } = props; const onBottomSheetChange = React.useCallback( (index: number) => { if (index === -1) { onClosed(); } }, [onClosed], ); let bottomSheet; if (nonce && primaryIdentityPublicKey) { bottomSheet = ( ); } const setLoadingProp = props.setLoading; const loading = !getSIWENonceCallFailed && (isLoading || siweAuthCallLoading); React.useEffect(() => { setLoadingProp(loading); }, [setLoadingProp, loading]); return bottomSheet; } export default SIWEPanel; diff --git a/native/backup/use-client-backup.js b/native/backup/use-client-backup.js index 197bdb90a..2c0be6169 100644 --- a/native/backup/use-client-backup.js +++ b/native/backup/use-client-backup.js @@ -1,69 +1,68 @@ // @flow import * as React from 'react'; import { useSelector } from 'react-redux'; import { uintArrayToHexString } from 'lib/media/data-utils.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; import type { BackupAuth, UserData, UserKeys } from 'lib/types/backup-types.js'; import { uploadBackup } from './api.js'; import { BACKUP_ID_LENGTH } from './constants.js'; import { encryptBackup } from './encryption.js'; import { commCoreModule } from '../native-modules.js'; import { generateKey } from '../utils/aes-crypto-module.js'; +import { getContentSigningKey } from '../utils/crypto-utils.js'; type ClientBackup = { +uploadBackupProtocol: (userData: UserData) => Promise, }; function useClientBackup(): ClientBackup { const accessToken = useSelector(state => state.commServicesAccessToken); const currentUserID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); const loggedIn = useSelector(isLoggedIn); const uploadBackupProtocol = React.useCallback( async (userData: UserData) => { if (!loggedIn || !currentUserID) { throw new Error('Attempt to upload backup for not logged in user.'); } console.info('Start uploading backup...'); const backupDataKey = generateKey(); - await commCoreModule.initializeCryptoAccount(); - const { - primaryIdentityPublicKeys: { ed25519 }, - } = await commCoreModule.getUserPublicKey(); + + const [ed25519, backupID] = await Promise.all([ + getContentSigningKey(), + commCoreModule.generateRandomString(BACKUP_ID_LENGTH), + ]); + const userKeys: UserKeys = { backupDataKey: uintArrayToHexString(backupDataKey), ed25519, }; - const backupID = await commCoreModule.generateRandomString( - BACKUP_ID_LENGTH, - ); - const encryptedBackup = await encryptBackup({ backupID, userKeys, userData, }); const backupAuth: BackupAuth = { userID: currentUserID, accessToken: accessToken ? accessToken : '', deviceID: ed25519, }; await uploadBackup(encryptedBackup, backupAuth); console.info('Backup uploaded.'); }, [accessToken, currentUserID, loggedIn], ); return { uploadBackupProtocol }; } export { useClientBackup }; diff --git a/native/utils/crypto-utils.js b/native/utils/crypto-utils.js index 54c8d6b8e..91cf445ad 100644 --- a/native/utils/crypto-utils.js +++ b/native/utils/crypto-utils.js @@ -1,55 +1,63 @@ // @flow import * as React from 'react'; import { getOlmSessionInitializationData, getOlmSessionInitializationDataActionTypes, } from 'lib/actions/user-actions.js'; import { useServerCall, useDispatchActionPromise, } from 'lib/utils/action-utils.js'; import type { CallServerEndpointOptions } from 'lib/utils/call-server-endpoint.js'; import { commCoreModule } from '../native-modules.js'; function useInitialNotificationsEncryptedMessage(): ( callServerEndpointOptions?: ?CallServerEndpointOptions, ) => Promise { const callGetOlmSessionInitializationData = useServerCall( getOlmSessionInitializationData, ); const dispatchActionPromise = useDispatchActionPromise(); return React.useCallback( async callServerEndpointOptions => { const olmSessionDataPromise = callGetOlmSessionInitializationData( callServerEndpointOptions, ); dispatchActionPromise( getOlmSessionInitializationDataActionTypes, olmSessionDataPromise, ); const { signedIdentityKeysBlob, notifInitializationInfo } = await olmSessionDataPromise; const { notificationIdentityPublicKeys } = JSON.parse( signedIdentityKeysBlob.payload, ); const { prekey, prekeySignature, oneTimeKey } = notifInitializationInfo; return await commCoreModule.initializeNotificationsSession( JSON.stringify(notificationIdentityPublicKeys), prekey, prekeySignature, oneTimeKey, ); }, [callGetOlmSessionInitializationData, dispatchActionPromise], ); } -export { useInitialNotificationsEncryptedMessage }; +async function getContentSigningKey(): Promise { + await commCoreModule.initializeCryptoAccount(); + const { + primaryIdentityPublicKeys: { ed25519 }, + } = await commCoreModule.getUserPublicKey(); + return ed25519; +} + +export { useInitialNotificationsEncryptedMessage, getContentSigningKey };