Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3387068
D11331.id38254.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
5 KB
Referenced Files
None
Subscribers
None
D11331.id38254.diff
View Options
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
@@ -1,19 +1,95 @@
// @flow
+import olm from '@commapp/olm';
+import invariant from 'invariant';
import { QRCodeSVG } from 'qrcode.react';
import * as React from 'react';
+import { createSelector } from 'reselect';
+import { identityLogInActionTypes } from 'lib/actions/user-actions.js';
+import { QRAuthHandler } from 'lib/components/qr-auth-handler.react.js';
import { qrCodeLinkURL } from 'lib/facts/links.js';
import { generateKeyCommon } from 'lib/media/aes-crypto-utils-common.js';
import { uintArrayToHexString } from 'lib/media/data-utils.js';
+import { IdentityClientContext } from 'lib/shared/identity-client-context.js';
+import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js';
+import type { CryptoStore, PickledOLMAccount } from 'lib/types/crypto-types.js';
+import type {
+ NonceChallenge,
+ SignedMessage,
+} from 'lib/types/identity-service-types.js';
+import type { WebAppState } from 'lib/types/redux-types.js';
+import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
import css from './qr-code-login.css';
+import { initOlm } from '../olm/olm-utils.js';
import { useSelector } from '../redux/redux-utils.js';
+const deviceIDAndPrimaryAccountSelector: (state: WebAppState) => {
+ ed25519Key: ?string,
+ primaryAccount: ?PickledOLMAccount,
+} = createSelector(
+ (state: WebAppState) => state.cryptoStore,
+ (cryptoStore: ?CryptoStore) => ({
+ ed25519Key: cryptoStore?.primaryIdentityKeys.ed25519,
+ primaryAccount: cryptoStore?.primaryAccount,
+ }),
+);
+
function QrCodeLogin(): React.Node {
const [qrCodeValue, setQrCodeValue] = React.useState<?string>();
- const ed25519Key = useSelector(
- state => state.cryptoStore?.primaryIdentityKeys.ed25519,
+ const { ed25519Key, primaryAccount } = useSelector(
+ deviceIDAndPrimaryAccountSelector,
+ );
+ const [deviceKeys, setDeviceKeys] =
+ React.useState<?{ +deviceID: string, +aesKey: string }>();
+ const { setUnauthorizedDeviceID } = useTunnelbroker();
+
+ const identityContext = React.useContext(IdentityClientContext);
+ const identityClient = identityContext?.identityClient;
+
+ const dispatchActionPromise = useDispatchActionPromise();
+ const performRegistration = React.useCallback(
+ async (userID: string) => {
+ if (!primaryAccount) {
+ return;
+ }
+ invariant(identityClient, 'identity context not set');
+ try {
+ await initOlm();
+ const primaryOLMAccount = new olm.Account();
+ primaryOLMAccount.unpickle(
+ primaryAccount.picklingKey,
+ primaryAccount.pickledAccount,
+ );
+
+ const nonce = await identityClient.generateNonce();
+ const nonceChallenge: NonceChallenge = { nonce };
+ const nonceMessage = JSON.stringify(nonceChallenge);
+ const signature = await primaryOLMAccount.sign(nonceMessage);
+ const challengeResponse: SignedMessage = {
+ message: nonceMessage,
+ signature,
+ };
+
+ await dispatchActionPromise(
+ identityLogInActionTypes,
+ identityClient.uploadKeysForRegisteredDeviceAndLogIn(
+ userID,
+ challengeResponse,
+ ),
+ );
+ setUnauthorizedDeviceID(null);
+ } catch (err) {
+ console.error('Secondary device registration error:', err);
+ }
+ },
+ [
+ dispatchActionPromise,
+ identityClient,
+ primaryAccount,
+ setUnauthorizedDeviceID,
+ ],
);
const generateQRCode = React.useCallback(async () => {
@@ -26,36 +102,45 @@
const aesKeyAsHexString: string = uintArrayToHexString(rawAESKey);
const url = qrCodeLinkURL(aesKeyAsHexString, ed25519Key);
+ setUnauthorizedDeviceID(ed25519Key);
setQrCodeValue(url);
+ setDeviceKeys({ deviceID: ed25519Key, aesKey: aesKeyAsHexString });
} catch (err) {
console.error('Failed to generate QR Code:', err);
}
- }, [ed25519Key]);
+ }, [ed25519Key, setUnauthorizedDeviceID]);
React.useEffect(() => {
void generateQRCode();
}, [generateQRCode]);
return (
- <div className={css.qrContainer}>
- <div className={css.title}>Log in to Comm</div>
- <div className={css.scanInstructions}>
- Open the Comm app on your phone and scan the QR code below
- </div>
- <QRCodeSVG value={qrCodeValue} size={300} marginSize={4} level="L" />
- <div className={css.instructionsContainer}>
- <div className={css.instructionsTitle}>How to find the scanner:</div>
- <div className={css.instructionsStep}>
- Go to <strong>Profile</strong>
- </div>
- <div className={css.instructionsStep}>
- Select <strong>Linked devices</strong>
+ <>
+ <QRAuthHandler
+ secondaryDeviceID={deviceKeys?.deviceID}
+ aesKey={deviceKeys?.aesKey}
+ performSecondaryDeviceRegistration={performRegistration}
+ />
+ <div className={css.qrContainer}>
+ <div className={css.title}>Log in to Comm</div>
+ <div className={css.scanInstructions}>
+ Open the Comm app on your phone and scan the QR code below
</div>
- <div className={css.instructionsStep}>
- Click <strong>Add</strong> on the top right
+ <QRCodeSVG value={qrCodeValue} size={300} marginSize={4} level="L" />
+ <div className={css.instructionsContainer}>
+ <div className={css.instructionsTitle}>How to find the scanner:</div>
+ <div className={css.instructionsStep}>
+ Go to <strong>Profile</strong>
+ </div>
+ <div className={css.instructionsStep}>
+ Select <strong>Linked devices</strong>
+ </div>
+ <div className={css.instructionsStep}>
+ Click <strong>Add</strong> on the top right
+ </div>
</div>
</div>
- </div>
+ </>
);
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 30, 7:31 AM (21 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2600122
Default Alt Text
D11331.id38254.diff (5 KB)
Attached To
Mode
D11331: [web] Add QR auth handler logic
Attached
Detach File
Event Timeline
Log In to Comment