diff --git a/keyserver/src/keyserver.js b/keyserver/src/keyserver.js --- a/keyserver/src/keyserver.js +++ b/keyserver/src/keyserver.js @@ -14,6 +14,7 @@ import './cron/cron.js'; import { qrCodeLinkURL } from 'lib/facts/links.js'; +import { identityDeviceTypes } from 'lib/types/identity-service-types.js'; import { isDev } from 'lib/utils/dev-utils.js'; import { ignorePromiseRejections } from 'lib/utils/promises.js'; @@ -119,7 +120,11 @@ console.log( '\nOpen the Comm app on your phone and scan the QR code below, or copy and paste this URL:\n', ); - const url = qrCodeLinkURL(aes256Key, ed25519Key); + const url = qrCodeLinkURL( + aes256Key, + ed25519Key, + identityDeviceTypes.KEYSERVER, + ); console.log(url, '\n'); console.log('How to find the scanner:\n'); console.log('Go to \x1b[1mProfile\x1b[0m'); diff --git a/lib/facts/links.js b/lib/facts/links.js --- a/lib/facts/links.js +++ b/lib/facts/links.js @@ -1,5 +1,9 @@ // @flow +import { + type IdentityDeviceType, + assertIdentityDeviceType, +} from '../types/identity-service-types.js'; import { isDev } from '../utils/dev-utils.js'; /* Invite Links */ @@ -11,13 +15,17 @@ } /* QR Code */ -function qrCodeLinkURL(aes256Param: string, ed25519Param: string): string { +function qrCodeLinkURL( + aes256Param: string, + ed25519Param: string, + deviceType: IdentityDeviceType, +): string { const keys = { aes256: aes256Param, ed25519: ed25519Param, }; const keysString = encodeURIComponent(JSON.stringify(keys)); - return `comm://qr-code/${keysString}`; + return `comm://qr-code/${deviceType}/${keysString}`; } /* Deep Link Utils */ @@ -33,13 +41,13 @@ }; type ParsedQRCodeData = { +type: 'qr-code', - +data: { +keys: string }, + +data: { +keys: string, +deviceType?: IdentityDeviceType }, }; export type ParsedDeepLinkData = ParsedInviteLinkData | ParsedQRCodeData | null; function parseDataFromDeepLink(url: string): ParsedDeepLinkData { const inviteLinkSecretRegex = /invite\/(\S+)$/; - const qrCodeKeysRegex = /qr-code\/(\S+)$/; + const qrCodeKeysRegex = /qr-code\/(?:([^/]+)\/)?(\S+)$/; const inviteLinkSecretMatch = inviteLinkSecretRegex.exec(url); if (inviteLinkSecretMatch) { @@ -50,14 +58,36 @@ } const qrCodeKeysMatch = qrCodeKeysRegex.exec(url); - if (qrCodeKeysMatch) { + if (!qrCodeKeysMatch) { + return null; + } + + const [, deviceTypeStr, keys] = qrCodeKeysMatch; + if (!deviceTypeStr) { return { type: 'qr-code', - data: { keys: qrCodeKeysMatch[1] }, + data: { keys }, }; } - return null; + const parsedDeviceTypeNumber = parseInt(deviceTypeStr); + try { + const deviceType = assertIdentityDeviceType(parsedDeviceTypeNumber); + return { + type: 'qr-code', + data: { + keys, + deviceType: deviceType, + }, + }; + } catch (e) { + console.warn('QR code contains invalid device type'); + + return { + type: 'qr-code', + data: { keys }, + }; + } } export { diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -1,5 +1,6 @@ // @flow +import invariant from 'invariant'; import t, { type TInterface, type TList, type TDict, type TEnums } from 'tcomb'; import { @@ -343,6 +344,27 @@ export type IdentityDeviceType = $Values; +function isIdentityDeviceType(deviceType: number): boolean %checks { + return ( + deviceType === 0 || + deviceType === 1 || + deviceType === 2 || + deviceType === 3 || + deviceType === 4 || + deviceType === 5 + ); +} + +export function assertIdentityDeviceType( + deviceType: number, +): IdentityDeviceType { + invariant( + isIdentityDeviceType(deviceType), + 'number is not IdentityDeviceType enum', + ); + return deviceType; +} + export const identityDeviceTypeToPlatform: { +[identityDeviceType: IdentityDeviceType]: Platform, } = Object.freeze({ 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 @@ -1,11 +1,12 @@ // @flow import * as React from 'react'; -import { View, Text } from 'react-native'; +import { View, Text, Platform } from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import { useQRAuthContext } from 'lib/components/qr-auth-provider.react.js'; import { qrCodeLinkURL } from 'lib/facts/links.js'; +import { platformToIdentityDeviceType } from 'lib/types/identity-service-types.js'; import type { QRCodeSignInNavigationProp } from './qr-code-sign-in-navigator.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; @@ -24,10 +25,14 @@ void generateQRCode(); }, [generateQRCode]); - const qrCodeURL = React.useMemo( - () => (qrData ? qrCodeLinkURL(qrData.aesKey, qrData.deviceID) : undefined), - [qrData], - ); + const qrCodeURL = React.useMemo(() => { + if (!qrData) { + return undefined; + } + + const deviceType = platformToIdentityDeviceType[Platform.OS]; + return qrCodeLinkURL(qrData.aesKey, qrData.deviceID, deviceType); + }, [qrData]); const styles = useStyles(unboundStyles); return ( 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 @@ -5,20 +5,28 @@ import { useQRAuthContext } from 'lib/components/qr-auth-provider.react.js'; import { qrCodeLinkURL } from 'lib/facts/links.js'; +import { platformToIdentityDeviceType } from 'lib/types/identity-service-types.js'; +import { getConfig } from 'lib/utils/config.js'; import css from './qr-code-login.css'; function QRCodeLogin(): React.Node { const { qrData, generateQRCode } = useQRAuthContext(); + const platform = getConfig().platformDetails.platform; React.useEffect(() => { void generateQRCode(); }, [generateQRCode]); - const qrCodeURL = React.useMemo( - () => (qrData ? qrCodeLinkURL(qrData.aesKey, qrData.deviceID) : undefined), - [qrData], - ); + const qrCodeURL = React.useMemo(() => { + if (!qrData) { + return undefined; + } + + const identityDeviceType = platformToIdentityDeviceType[platform]; + + return qrCodeLinkURL(qrData.aesKey, qrData.deviceID, identityDeviceType); + }, [platform, qrData]); return (