diff --git a/native/qr-code/qr-code-screen.react.js b/native/qr-code/qr-code-screen.react.js index d897f0426..ee25b7175 100644 --- a/native/qr-code/qr-code-screen.react.js +++ b/native/qr-code/qr-code-screen.react.js @@ -1,166 +1,167 @@ // @flow import * as React from 'react'; import { View, Text } from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import { qrCodeLinkURL } from 'lib/facts/links.js'; import { useSecondaryDeviceLogIn } from 'lib/hooks/login-hooks.js'; 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 type { QRCodeSignInNavigationProp } from './qr-code-sign-in-navigator.react.js'; import { composeTunnelbrokerQRAuthMessage, parseTunnelbrokerQRAuthMessage, } from './qr-code-utils.js'; import { commCoreModule } from '../native-modules.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useStyles } from '../themes/colors.js'; import * as AES from '../utils/aes-crypto-module.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, ); } // eslint-disable-next-line no-unused-vars function QRCodeScreen(props: QRCodeScreenProps): React.Node { const [qrData, setQRData] = React.useState(); - const logInSecondaryDevice = useSecondaryDeviceLogIn(); - const performRegistration = React.useCallback( - async (userID: string) => { - try { - await logInSecondaryDevice(userID); - } catch (err) { - console.error('Secondary device registration error:', err); - Alert.alert('Registration failed', 'Failed to upload device keys', [ - { text: 'OK' }, - ]); - } - }, - [logInSecondaryDevice], - ); - const { setUnauthorizedDeviceID } = useTunnelbroker(); const generateQRCode = React.useCallback(async () => { try { const [ed25519, rawAESKey] = await Promise.all([ getContentSigningKey(), AES.generateKey(), ]); const aesKeyAsHexString: string = uintArrayToHexString(rawAESKey); setUnauthorizedDeviceID(ed25519); setQRData({ deviceID: ed25519, aesKey: aesKeyAsHexString }); } catch (err) { console.error('Failed to generate QR Code:', err); } }, [setUnauthorizedDeviceID]); + const logInSecondaryDevice = useSecondaryDeviceLogIn(); + const performRegistration = React.useCallback( + async (userID: string) => { + try { + await logInSecondaryDevice(userID); + } catch (err) { + console.error('Secondary device registration error:', err); + Alert.alert('Registration failed', 'Failed to upload device keys', [ + { text: 'OK' }, + ]); + void generateQRCode(); + } + }, + [logInSecondaryDevice, generateQRCode], + ); + React.useEffect(() => { void generateQRCode(); }, [generateQRCode]); const qrCodeURL = React.useMemo( () => (qrData ? qrCodeLinkURL(qrData.aesKey, qrData.deviceID) : undefined), [qrData], ); const qrAuthInput = React.useMemo( () => ({ secondaryDeviceID: qrData?.deviceID, aesKey: qrData?.aesKey, performSecondaryDeviceRegistration: performRegistration, composeMessage: composeTunnelbrokerQRAuthMessage, processMessage: parseTunnelbrokerQRAuthMessage, performBackupRestore, }), [qrData, performRegistration], ); useQRAuth(qrAuthInput); const styles = useStyles(unboundStyles); return ( Log in to Comm Open the Comm app on your phone and scan the QR code below How to find the scanner: Go to Profile Select Linked devices Click Add on the top right ); } const unboundStyles = { container: { flex: 1, alignItems: 'center', marginTop: 125, }, heading: { fontSize: 24, color: 'panelForegroundLabel', paddingBottom: 12, }, headingSubtext: { fontSize: 12, color: 'panelForegroundLabel', paddingBottom: 30, }, instructionsBox: { alignItems: 'center', width: 300, marginTop: 40, padding: 15, borderColor: 'panelForegroundLabel', borderWidth: 2, borderRadius: 8, }, instructionsTitle: { fontSize: 12, color: 'panelForegroundLabel', paddingBottom: 15, }, instructionsStep: { fontSize: 12, padding: 1, color: 'panelForegroundLabel', }, instructionsBold: { fontWeight: 'bold', }, }; export default QRCodeScreen; diff --git a/web/account/qr-code-login.react.js b/web/account/qr-code-login.react.js index 29da7e877..5b8161aff 100644 --- a/web/account/qr-code-login.react.js +++ b/web/account/qr-code-login.react.js @@ -1,141 +1,142 @@ // @flow import { QRCodeSVG } from 'qrcode.react'; import * as React from 'react'; 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 { 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 { getContentSigningKey } from 'lib/utils/crypto-utils.js'; import css from './qr-code-login.css'; import { base64DecodeBuffer, base64EncodeBuffer, } from '../utils/base64-utils.js'; import { convertBytesToObj, convertObjToBytes, } from '../utils/conversion-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; } function QRCodeLogin(): React.Node { const [qrData, setQRData] = React.useState(); - const logInSecondaryDevice = useSecondaryDeviceLogIn(); - const performRegistration = React.useCallback( - async (userID: string) => { - try { - await logInSecondaryDevice(userID); - } catch (err) { - console.error('Secondary device registration error:', err); - } - }, - [logInSecondaryDevice], - ); - const { setUnauthorizedDeviceID } = useTunnelbroker(); const generateQRCode = React.useCallback(async () => { try { const [ed25519, rawAESKey] = await Promise.all([ getContentSigningKey(), generateKeyCommon(crypto), ]); const aesKeyAsHexString: string = uintArrayToHexString(rawAESKey); setUnauthorizedDeviceID(ed25519); setQRData({ deviceID: ed25519, aesKey: aesKeyAsHexString }); } catch (err) { console.error('Failed to generate QR Code:', err); } }, [setUnauthorizedDeviceID]); + const logInSecondaryDevice = useSecondaryDeviceLogIn(); + const performRegistration = React.useCallback( + async (userID: string) => { + try { + await logInSecondaryDevice(userID); + } catch (err) { + console.error('Secondary device registration error:', err); + void generateQRCode(); + } + }, + [logInSecondaryDevice, generateQRCode], + ); + React.useEffect(() => { void generateQRCode(); }, [generateQRCode]); const qrCodeURL = React.useMemo( () => (qrData ? qrCodeLinkURL(qrData.aesKey, qrData.deviceID) : undefined), [qrData], ); const qrAuthInput = React.useMemo( () => ({ secondaryDeviceID: qrData?.deviceID, aesKey: qrData?.aesKey, performSecondaryDeviceRegistration: performRegistration, composeMessage: composeTunnelbrokerMessage, processMessage: parseTunnelbrokerMessage, }), [qrData, performRegistration], ); useQRAuth(qrAuthInput); return (
Log in to Comm
Open the Comm app on your phone and scan the QR code below
How to find the scanner:
Go to Profile
Select Linked devices
Click Add on the top right
); } export default QRCodeLogin;