diff --git a/native/qr-code/qr-code-screen.react.js b/native/qr-code/qr-code-screen.react.js --- a/native/qr-code/qr-code-screen.react.js +++ b/native/qr-code/qr-code-screen.react.js @@ -9,41 +9,24 @@ import { useQRAuth } from 'lib/hooks/qr-auth.js'; import { uintArrayToHexString } from 'lib/media/data-utils.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; -import type { BackupKeys } from 'lib/types/backup-types.js'; import { getContentSigningKey } from 'lib/utils/crypto-utils.js'; -import { getMessageForException } from 'lib/utils/errors.js'; import type { QRCodeSignInNavigationProp } from './qr-code-sign-in-navigator.react.js'; import { composeTunnelbrokerQRAuthMessage, + handleSecondaryDeviceRegistrationError, parseTunnelbrokerQRAuthMessage, + performBackupRestore, } from './qr-code-utils.js'; -import { commCoreModule } from '../native-modules.js'; import type { NavigationRoute } from '../navigation/route-names.js'; -import { persistConfig } from '../redux/persist.js'; import { useStyles } from '../themes/colors.js'; import * as AES from '../utils/aes-crypto-module.js'; -import { - appOutOfDateAlertDetails, - unknownErrorAlertDetails, -} from '../utils/alert-messages.js'; -import Alert from '../utils/alert.js'; type QRCodeScreenProps = { +navigation: QRCodeSignInNavigationProp<'QRCodeScreen'>, +route: NavigationRoute<'QRCodeScreen'>, }; -function performBackupRestore(backupKeys: BackupKeys): Promise { - const { backupID, backupDataKey, backupLogDataKey } = backupKeys; - return commCoreModule.restoreBackupData( - backupID, - backupDataKey, - backupLogDataKey, - persistConfig.version.toString(), - ); -} - // eslint-disable-next-line no-unused-vars function QRCodeScreen(props: QRCodeScreenProps): React.Node { const [qrData, setQRData] = @@ -71,22 +54,7 @@ try { await logInSecondaryDevice(userID); } catch (err) { - console.error('Secondary device registration error:', err); - const messageForException = getMessageForException(err); - if ( - messageForException === 'client_version_unsupported' || - messageForException === 'unsupported_version' - ) { - Alert.alert( - appOutOfDateAlertDetails.title, - appOutOfDateAlertDetails.message, - ); - } else { - Alert.alert( - unknownErrorAlertDetails.title, - unknownErrorAlertDetails.message, - ); - } + handleSecondaryDeviceRegistrationError(err); void generateQRCode(); } }, diff --git a/native/qr-code/qr-code-utils.js b/native/qr-code/qr-code-utils.js --- a/native/qr-code/qr-code-utils.js +++ b/native/qr-code/qr-code-utils.js @@ -1,6 +1,7 @@ // @flow import { hexToUintArray } from 'lib/media/data-utils.js'; +import type { BackupKeys } from 'lib/types/backup-types.js'; import { peerToPeerMessageTypes, type QRCodeAuthMessage, @@ -9,13 +10,20 @@ qrCodeAuthMessagePayloadValidator, type QRCodeAuthMessagePayload, } from 'lib/types/tunnelbroker/qr-code-auth-message-types.js'; +import { getMessageForException } from 'lib/utils/errors.js'; import { convertBytesToObj, convertObjToBytes, } from '../backup/conversion-utils.js'; -import { commUtilsModule } from '../native-modules.js'; +import { commCoreModule, commUtilsModule } from '../native-modules.js'; +import { persistConfig } from '../redux/persist.js'; import * as AES from '../utils/aes-crypto-module.js'; +import { + appOutOfDateAlertDetails, + unknownErrorAlertDetails, +} from '../utils/alert-messages.js'; +import Alert from '../utils/alert.js'; function composeTunnelbrokerQRAuthMessage( encryptionKey: string, @@ -52,4 +60,38 @@ return Promise.resolve(payload); } -export { composeTunnelbrokerQRAuthMessage, parseTunnelbrokerQRAuthMessage }; +function handleSecondaryDeviceRegistrationError(error: mixed): void { + console.error('Secondary device registration error:', error); + const messageForException = getMessageForException(error); + if ( + messageForException === 'client_version_unsupported' || + messageForException === 'unsupported_version' + ) { + Alert.alert( + appOutOfDateAlertDetails.title, + appOutOfDateAlertDetails.message, + ); + } else { + Alert.alert( + unknownErrorAlertDetails.title, + unknownErrorAlertDetails.message, + ); + } +} + +function performBackupRestore(backupKeys: BackupKeys): Promise { + const { backupID, backupDataKey, backupLogDataKey } = backupKeys; + return commCoreModule.restoreBackupData( + backupID, + backupDataKey, + backupLogDataKey, + persistConfig.version.toString(), + ); +} + +export { + composeTunnelbrokerQRAuthMessage, + parseTunnelbrokerQRAuthMessage, + handleSecondaryDeviceRegistrationError, + performBackupRestore, +}; diff --git a/web/account/qr-code-login.react.js b/web/account/qr-code-login.react.js --- a/web/account/qr-code-login.react.js +++ b/web/account/qr-code-login.react.js @@ -3,68 +3,20 @@ import { QRCodeSVG } from 'qrcode.react'; import * as React from 'react'; -import { useModalContext } from 'lib/components/modal-provider.react.js'; import { qrCodeLinkURL } from 'lib/facts/links.js'; import { useSecondaryDeviceLogIn } from 'lib/hooks/login-hooks.js'; import { useQRAuth } from 'lib/hooks/qr-auth.js'; import { generateKeyCommon } from 'lib/media/aes-crypto-utils-common.js'; -import * as AES from 'lib/media/aes-crypto-utils-common.js'; -import { hexToUintArray, uintArrayToHexString } from 'lib/media/data-utils.js'; +import { uintArrayToHexString } from 'lib/media/data-utils.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; -import { - peerToPeerMessageTypes, - type QRCodeAuthMessage, -} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; -import { - qrCodeAuthMessagePayloadValidator, - type QRCodeAuthMessagePayload, -} from 'lib/types/tunnelbroker/qr-code-auth-message-types.js'; -import { - convertBytesToObj, - convertObjToBytes, -} from 'lib/utils/conversion-utils.js'; import { getContentSigningKey } from 'lib/utils/crypto-utils.js'; -import { getMessageForException } from 'lib/utils/errors.js'; import css from './qr-code-login.css'; -import Alert from '../modals/alert.react.js'; -import VersionUnsupportedModal from '../modals/version-unsupported-modal.react.js'; import { - base64DecodeBuffer, - base64EncodeBuffer, -} from '../utils/base64-utils.js'; - -async function composeTunnelbrokerMessage( - encryptionKey: string, - obj: QRCodeAuthMessagePayload, -): Promise { - const objBytes = convertObjToBytes(obj); - const keyBytes = hexToUintArray(encryptionKey); - const encryptedBytes = await AES.encryptCommon(crypto, keyBytes, objBytes); - const encryptedContent = base64EncodeBuffer(encryptedBytes); - return { - type: peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE, - encryptedContent, - }; -} - -async function parseTunnelbrokerMessage( - encryptionKey: string, - message: QRCodeAuthMessage, -): Promise { - const encryptedData = base64DecodeBuffer(message.encryptedContent); - const decryptedData = await AES.decryptCommon( - crypto, - hexToUintArray(encryptionKey), - new Uint8Array(encryptedData), - ); - const payload = convertBytesToObj(decryptedData); - if (!qrCodeAuthMessagePayloadValidator.is(payload)) { - return null; - } - - return payload; -} + composeTunnelbrokerQRAuthMessage, + parseTunnelbrokerQRAuthMessage, + useHandleSecondaryDeviceRegistrationError, +} from '../utils/qr-code-utils.js'; function QRCodeLogin(): React.Node { const [qrData, setQRData] = @@ -86,7 +38,7 @@ } }, [setUnauthorizedDeviceID]); - const { pushModal } = useModalContext(); + const handleError = useHandleSecondaryDeviceRegistrationError(); const logInSecondaryDevice = useSecondaryDeviceLogIn(); const performRegistration = React.useCallback( @@ -94,20 +46,11 @@ try { await logInSecondaryDevice(userID); } catch (err) { - console.error('Secondary device registration error:', err); - const messageForException = getMessageForException(err); - if ( - messageForException === 'client_version_unsupported' || - messageForException === 'unsupported_version' - ) { - pushModal(); - } else { - pushModal(Uhh... try again?); - } + handleError(err); void generateQRCode(); } }, - [logInSecondaryDevice, pushModal, generateQRCode], + [logInSecondaryDevice, handleError, generateQRCode], ); React.useEffect(() => { @@ -124,8 +67,8 @@ secondaryDeviceID: qrData?.deviceID, aesKey: qrData?.aesKey, performSecondaryDeviceRegistration: performRegistration, - composeMessage: composeTunnelbrokerMessage, - processMessage: parseTunnelbrokerMessage, + composeMessage: composeTunnelbrokerQRAuthMessage, + processMessage: parseTunnelbrokerQRAuthMessage, }), [qrData, performRegistration], ); diff --git a/web/utils/qr-code-utils.js b/web/utils/qr-code-utils.js new file mode 100644 --- /dev/null +++ b/web/utils/qr-code-utils.js @@ -0,0 +1,81 @@ +// @flow + +import * as React from 'react'; + +import { useModalContext } from 'lib/components/modal-provider.react.js'; +import * as AES from 'lib/media/aes-crypto-utils-common.js'; +import { hexToUintArray } from 'lib/media/data-utils.js'; +import { + peerToPeerMessageTypes, + type QRCodeAuthMessage, +} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; +import { + qrCodeAuthMessagePayloadValidator, + type QRCodeAuthMessagePayload, +} from 'lib/types/tunnelbroker/qr-code-auth-message-types.js'; +import { + convertBytesToObj, + convertObjToBytes, +} from 'lib/utils/conversion-utils.js'; +import { getMessageForException } from 'lib/utils/errors.js'; + +import { base64DecodeBuffer, base64EncodeBuffer } from './base64-utils.js'; +import Alert from '../modals/alert.react.js'; +import VersionUnsupportedModal from '../modals/version-unsupported-modal.react.js'; + +async function composeTunnelbrokerQRAuthMessage( + encryptionKey: string, + obj: QRCodeAuthMessagePayload, +): Promise { + const objBytes = convertObjToBytes(obj); + const keyBytes = hexToUintArray(encryptionKey); + const encryptedBytes = await AES.encryptCommon(crypto, keyBytes, objBytes); + const encryptedContent = base64EncodeBuffer(encryptedBytes); + return { + type: peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE, + encryptedContent, + }; +} + +async function parseTunnelbrokerQRAuthMessage( + encryptionKey: string, + message: QRCodeAuthMessage, +): Promise { + const encryptedData = base64DecodeBuffer(message.encryptedContent); + const decryptedData = await AES.decryptCommon( + crypto, + hexToUintArray(encryptionKey), + new Uint8Array(encryptedData), + ); + const payload = convertBytesToObj(decryptedData); + if (!qrCodeAuthMessagePayloadValidator.is(payload)) { + return null; + } + + return payload; +} + +function useHandleSecondaryDeviceRegistrationError(): (error: mixed) => void { + const { pushModal } = useModalContext(); + return React.useCallback( + (error: mixed) => { + console.error('Secondary device registration error:', error); + const messageForException = getMessageForException(error); + if ( + messageForException === 'client_version_unsupported' || + messageForException === 'unsupported_version' + ) { + pushModal(); + } else { + pushModal(Uhh... try again?); + } + }, + [pushModal], + ); +} + +export { + composeTunnelbrokerQRAuthMessage, + parseTunnelbrokerQRAuthMessage, + useHandleSecondaryDeviceRegistrationError, +};