diff --git a/lib/components/qr-auth-provider.react.js b/lib/components/qr-auth-provider.react.js --- a/lib/components/qr-auth-provider.react.js +++ b/lib/components/qr-auth-provider.react.js @@ -4,12 +4,23 @@ import * as React from 'react'; import { useSecondaryDeviceLogIn } from '../hooks/login-hooks.js'; -import { useQRAuth } from '../hooks/qr-auth.js'; import { uintArrayToHexString } from '../media/data-utils.js'; +import { IdentityClientContext } from '../shared/identity-client-context.js'; import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js'; import type { BackupKeys } from '../types/backup-types.js'; -import type { QRCodeAuthMessage } from '../types/tunnelbroker/peer-to-peer-message-types.js'; -import type { QRCodeAuthMessagePayload } from '../types/tunnelbroker/qr-code-auth-message-types.js'; +import { + tunnelbrokerMessageTypes, + type TunnelbrokerMessage, +} from '../types/tunnelbroker/messages.js'; +import { + type QRCodeAuthMessage, + peerToPeerMessageTypes, + peerToPeerMessageValidator, +} from '../types/tunnelbroker/peer-to-peer-message-types.js'; +import { + qrCodeAuthMessageTypes, + type QRCodeAuthMessagePayload, +} from '../types/tunnelbroker/qr-code-auth-message-types.js'; import { getContentSigningKey } from '../utils/crypto-utils.js'; type Props = { @@ -50,9 +61,19 @@ performBackupRestore, } = props; + const [primaryDeviceID, setPrimaryDeviceID] = React.useState(); const [qrData, setQRData] = React.useState(); - const { setUnauthorizedDeviceID } = useTunnelbroker(); + const { + setUnauthorizedDeviceID, + addListener, + removeListener, + socketState, + sendMessage, + } = useTunnelbroker(); + + const identityContext = React.useContext(IdentityClientContext); + const identityClient = identityContext?.identityClient; const generateQRCode = React.useCallback(async () => { try { @@ -82,24 +103,116 @@ [logInSecondaryDevice, onLoginError, generateQRCode], ); - const qrAuthInput = React.useMemo( - () => ({ - secondaryDeviceID: qrData?.deviceID, - aesKey: qrData?.aesKey, - performSecondaryDeviceRegistration: performLogIn, - composeMessage: composeTunnelbrokerMessage, - processMessage: processTunnelbrokerMessage, - performBackupRestore, - }), + React.useEffect(() => { + if (!qrData || !socketState.isAuthorized || !primaryDeviceID) { + return; + } + + void (async () => { + const message = await composeTunnelbrokerMessage(qrData?.aesKey, { + type: qrCodeAuthMessageTypes.SECONDARY_DEVICE_REGISTRATION_SUCCESS, + requestBackupKeys: true, + }); + await sendMessage({ + deviceID: primaryDeviceID, + payload: JSON.stringify(message), + }); + })(); + }, [ + sendMessage, + primaryDeviceID, + qrData, + socketState, + composeTunnelbrokerMessage, + ]); + + const tunnelbrokerMessageListener = React.useCallback( + async (message: TunnelbrokerMessage) => { + invariant(identityClient, 'identity context not set'); + if ( + !qrData?.aesKey || + message.type !== tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE + ) { + return; + } + + let innerMessage; + try { + innerMessage = JSON.parse(message.payload); + } catch { + return; + } + if ( + !peerToPeerMessageValidator.is(innerMessage) || + innerMessage.type !== peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE + ) { + return; + } + + let qrCodeAuthMessage; + try { + qrCodeAuthMessage = await processTunnelbrokerMessage( + qrData?.aesKey, + innerMessage, + ); + } catch (err) { + console.warn('Failed to decrypt Tunnelbroker QR auth message:', err); + return; + } + + if ( + qrCodeAuthMessage && + qrCodeAuthMessage.type === + qrCodeAuthMessageTypes.BACKUP_DATA_KEY_MESSAGE + ) { + const { backupID, backupDataKey, backupLogDataKey } = qrCodeAuthMessage; + void performBackupRestore?.({ + backupID, + backupDataKey, + backupLogDataKey, + }); + return; + } + + if ( + !qrCodeAuthMessage || + qrCodeAuthMessage.type !== + qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS + ) { + return; + } + const { primaryDeviceID: receivedPrimaryDeviceID, userID } = + qrCodeAuthMessage; + setPrimaryDeviceID(receivedPrimaryDeviceID); + + await performLogIn(userID); + setUnauthorizedDeviceID(null); + }, [ - qrData, + identityClient, + qrData?.aesKey, performLogIn, - composeTunnelbrokerMessage, + setUnauthorizedDeviceID, processTunnelbrokerMessage, performBackupRestore, ], ); - useQRAuth(qrAuthInput); + + React.useEffect(() => { + if (!qrData?.deviceID) { + return undefined; + } + + addListener(tunnelbrokerMessageListener); + return () => { + removeListener(tunnelbrokerMessageListener); + }; + }, [ + addListener, + removeListener, + tunnelbrokerMessageListener, + qrData?.deviceID, + ]); const value = React.useMemo( () => ({ diff --git a/lib/hooks/qr-auth.js b/lib/hooks/qr-auth.js deleted file mode 100644 --- a/lib/hooks/qr-auth.js +++ /dev/null @@ -1,174 +0,0 @@ -// @flow - -import invariant from 'invariant'; -import * as React from 'react'; - -import { IdentityClientContext } from '../shared/identity-client-context.js'; -import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js'; -import type { BackupKeys } from '../types/backup-types.js'; -import { - tunnelbrokerMessageTypes, - type TunnelbrokerMessage, -} from '../types/tunnelbroker/messages.js'; -import { - peerToPeerMessageTypes, - peerToPeerMessageValidator, - type QRCodeAuthMessage, -} from '../types/tunnelbroker/peer-to-peer-message-types.js'; -import { - qrCodeAuthMessageTypes, - type QRCodeAuthMessagePayload, -} from '../types/tunnelbroker/qr-code-auth-message-types.js'; - -type QRAuthHandlerInput = { - +secondaryDeviceID: ?string, - +aesKey: ?string, - +performSecondaryDeviceRegistration: (userID: string) => Promise, - +composeMessage: ( - encryptionKey: string, - payload: QRCodeAuthMessagePayload, - ) => Promise, - +processMessage: ( - encryptionKey: string, - message: QRCodeAuthMessage, - ) => Promise, - +performBackupRestore?: (backupKeys: BackupKeys) => Promise, -}; - -function useQRAuth(input: QRAuthHandlerInput) { - const { - secondaryDeviceID, - aesKey, - processMessage, - composeMessage, - performSecondaryDeviceRegistration, - performBackupRestore, - } = input; - const [primaryDeviceID, setPrimaryDeviceID] = React.useState(); - const { - setUnauthorizedDeviceID, - addListener, - removeListener, - socketState, - sendMessage, - } = useTunnelbroker(); - - const identityContext = React.useContext(IdentityClientContext); - const identityClient = identityContext?.identityClient; - - React.useEffect(() => { - if ( - !secondaryDeviceID || - !aesKey || - !socketState.isAuthorized || - !primaryDeviceID - ) { - return; - } - - void (async () => { - const message = await composeMessage(aesKey, { - type: qrCodeAuthMessageTypes.SECONDARY_DEVICE_REGISTRATION_SUCCESS, - requestBackupKeys: true, - }); - await sendMessage({ - deviceID: primaryDeviceID, - payload: JSON.stringify(message), - }); - })(); - }, [ - sendMessage, - primaryDeviceID, - aesKey, - secondaryDeviceID, - composeMessage, - socketState, - ]); - - const tunnelbrokerMessageListener = React.useCallback( - async (message: TunnelbrokerMessage) => { - invariant(identityClient, 'identity context not set'); - if ( - !aesKey || - message.type !== tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE - ) { - return; - } - - let innerMessage; - try { - innerMessage = JSON.parse(message.payload); - } catch { - return; - } - if ( - !peerToPeerMessageValidator.is(innerMessage) || - innerMessage.type !== peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE - ) { - return; - } - - let qrCodeAuthMessage; - try { - qrCodeAuthMessage = await processMessage(aesKey, innerMessage); - } catch (err) { - console.warn('Failed to decrypt Tunnelbroker QR auth message:', err); - return; - } - - if ( - qrCodeAuthMessage && - qrCodeAuthMessage.type === - qrCodeAuthMessageTypes.BACKUP_DATA_KEY_MESSAGE - ) { - const { backupID, backupDataKey, backupLogDataKey } = qrCodeAuthMessage; - void performBackupRestore?.({ - backupID, - backupDataKey, - backupLogDataKey, - }); - return; - } - - if ( - !qrCodeAuthMessage || - qrCodeAuthMessage.type !== - qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS - ) { - return; - } - const { primaryDeviceID: receivedPrimaryDeviceID, userID } = - qrCodeAuthMessage; - setPrimaryDeviceID(receivedPrimaryDeviceID); - - await performSecondaryDeviceRegistration(userID); - setUnauthorizedDeviceID(null); - }, - [ - setUnauthorizedDeviceID, - identityClient, - aesKey, - performSecondaryDeviceRegistration, - performBackupRestore, - processMessage, - ], - ); - - React.useEffect(() => { - if (!secondaryDeviceID) { - return undefined; - } - - addListener(tunnelbrokerMessageListener); - return () => { - removeListener(tunnelbrokerMessageListener); - }; - }, [ - secondaryDeviceID, - addListener, - removeListener, - tunnelbrokerMessageListener, - ]); -} - -export { useQRAuth };