From 4998d1b3598d5d4e007dc9b4ec87d839a76dc84f Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Tue, 18 Jul 2017 23:46:03 +0100 Subject: [PATCH] Prepare for asynchronous e2e APIs the js-sdk is making some of its APIs asynchronous, and adding an `initCrypto` method which you have to call. Particular methods we need to worry about are: * `getStoredDevice` * `getStoredDevicesForUser` * `getEventSenderDeviceInfo` * `isEventSenderVerified` --- src/MatrixClientPeg.js | 12 +++ src/SlashCommands.js | 87 ++++++++++--------- .../views/dialogs/EncryptedEventDialog.js | 32 ++++--- src/components/views/rooms/EventTile.js | 9 +- src/components/views/rooms/MemberInfo.js | 15 ++-- 5 files changed, 93 insertions(+), 62 deletions(-) diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index b31cf7511e..4264828c7b 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -78,6 +78,18 @@ class MatrixClientPeg { } async start() { + // try to initialise e2e on the new client + try { + // check that we have a version of the js-sdk which includes initCrypto + if (this.matrixClient.initCrypto) { + await this.matrixClient.initCrypto(); + } + } catch(e) { + // this can happen for a number of reasons, the most likely being + // that the olm library was missing. It's not fatal. + console.warn("Unable to initialise e2e: " + e); + } + const opts = utils.deepCopy(this.opts); // the react sdk doesn't work without this, so don't allow opts.pendingEventOrdering = "detached"; diff --git a/src/SlashCommands.js b/src/SlashCommands.js index b1cd59f3a9..dea3d27751 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -301,51 +301,54 @@ const commands = { const deviceId = matches[2]; const fingerprint = matches[3]; - const device = MatrixClientPeg.get().getStoredDevice(userId, deviceId); - if (!device) { - return reject(_t(`Unknown (user, device) pair:`) + ` (${userId}, ${deviceId})`); - } + return success( + // Promise.resolve to handle transition from static result to promise; can be removed + // in future + Promise.resolve(MatrixClientPeg.get().getStoredDevice(userId, deviceId)).then((device) => { + if (!device) { + throw new Error(_t(`Unknown (user, device) pair:`) + ` (${userId}, ${deviceId})`); + } - if (device.isVerified()) { - if (device.getFingerprint() === fingerprint) { - return reject(_t(`Device already verified!`)); - } else { - return reject(_t(`WARNING: Device already verified, but keys do NOT MATCH!`)); - } - } + if (device.isVerified()) { + if (device.getFingerprint() === fingerprint) { + throw new Error(_t(`Device already verified!`)); + } else { + throw new Error(_t(`WARNING: Device already verified, but keys do NOT MATCH!`)); + } + } - if (device.getFingerprint() === fingerprint) { - MatrixClientPeg.get().setDeviceVerified( - userId, deviceId, true, - ); + if (device.getFingerprint() !== fingerprint) { + const fprint = device.getFingerprint(); + throw new Error( + _t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' + + ' %(deviceId)s is "%(fprint)s" which does not match the provided key' + + ' "%(fingerprint)s". This could mean your communications are being intercepted!', + {deviceId: deviceId, fprint: fprint, userId: userId, fingerprint: fingerprint})); + } - // Tell the user we verified everything! - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { - title: _t("Verified key"), - description: ( -
-

- { - _t("The signing key you provided matches the signing key you received " + - "from %(userId)s's device %(deviceId)s. Device marked as verified.", - {userId: userId, deviceId: deviceId}) - } -

-
- ), - hasCancelButton: false, - }); - - return success(); - } else { - const fprint = device.getFingerprint(); - return reject( - _t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' + - ' %(deviceId)s is "%(fprint)s" which does not match the provided key' + - ' "%(fingerprint)s". This could mean your communications are being intercepted!', - {deviceId: deviceId, fprint: fprint, userId: userId, fingerprint: fingerprint})); - } + return MatrixClientPeg.get().setDeviceVerified( + userId, deviceId, true, + ); + }).then(() => { + // Tell the user we verified everything + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createDialog(QuestionDialog, { + title: _t("Verified key"), + description: ( +
+

+ { + _t("The signing key you provided matches the signing key you received " + + "from %(userId)s's device %(deviceId)s. Device marked as verified.", + {userId: userId, deviceId: deviceId}) + } +

+
+ ), + hasCancelButton: false, + }); + }), + ); } } return reject(this.getUsage()); diff --git a/src/async-components/views/dialogs/EncryptedEventDialog.js b/src/async-components/views/dialogs/EncryptedEventDialog.js index 3a6ca4e6b7..cec2f05de2 100644 --- a/src/async-components/views/dialogs/EncryptedEventDialog.js +++ b/src/async-components/views/dialogs/EncryptedEventDialog.js @@ -28,23 +28,31 @@ module.exports = React.createClass({ }, getInitialState: function() { - return { device: this.refreshDevice() }; + return { device: null }; }, componentWillMount: function() { this._unmounted = false; var client = MatrixClientPeg.get(); - client.on("deviceVerificationChanged", this.onDeviceVerificationChanged); - // no need to redownload keys if we already have the device - if (this.state.device) { - return; - } - client.downloadKeys([this.props.event.getSender()], true).done(()=>{ + // first try to load the device from our store. + // + this.refreshDevice().then((dev) => { + if (dev) { + return dev; + } + + // tell the client to try to refresh the device list for this user + return client.downloadKeys([this.props.event.getSender()], true).then(() => { + return this.refreshDevice(); + }); + }).then((dev) => { if (this._unmounted) { return; } - this.setState({ device: this.refreshDevice() }); + + this.setState({ device: dev }); + client.on("deviceVerificationChanged", this.onDeviceVerificationChanged); }, (err)=>{ console.log("Error downloading devices", err); }); @@ -59,12 +67,16 @@ module.exports = React.createClass({ }, refreshDevice: function() { - return MatrixClientPeg.get().getEventSenderDeviceInfo(this.props.event); + // Promise.resolve to handle transition from static result to promise; can be removed + // in future + return Promise.resolve(MatrixClientPeg.get().getEventSenderDeviceInfo(this.props.event)); }, onDeviceVerificationChanged: function(userId, device) { if (userId == this.props.event.getSender()) { - this.setState({ device: this.refreshDevice() }); + this.refreshDevice().then((dev) => { + this.setState({ device: dev }); + }); } }, diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index bb085279e8..68ef3a1f44 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -193,13 +193,12 @@ module.exports = withMatrixClient(React.createClass({ } }, - _verifyEvent: function(mxEvent) { - var verified = null; - - if (mxEvent.isEncrypted()) { - verified = this.props.matrixClient.isEventSenderVerified(mxEvent); + _verifyEvent: async function(mxEvent) { + if (!mxEvent.isEncrypted()) { + return; } + const verified = await this.props.matrixClient.isEventSenderVerified(mxEvent); this.setState({ verified: verified }); diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index c034f0e704..290bd35483 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -136,8 +136,12 @@ module.exports = withMatrixClient(React.createClass({ if (userId == this.props.member.userId) { // no need to re-download the whole thing; just update our copy of // the list. - var devices = this.props.matrixClient.getStoredDevicesForUser(userId); - this.setState({devices: devices}); + + // Promise.resolve to handle transition from static result to promise; can be removed + // in future + Promise.resolve(this.props.matrixClient.getStoredDevicesForUser(userId)).then((devices) => { + this.setState({devices: devices}); + }); } }, @@ -204,14 +208,15 @@ module.exports = withMatrixClient(React.createClass({ var client = this.props.matrixClient; var self = this; - client.downloadKeys([member.userId], true).finally(function() { + client.downloadKeys([member.userId], true).then(() => { + return client.getStoredDevicesForUser(member.userId); + }).finally(function() { self._cancelDeviceList = null; - }).done(function() { + }).done(function(devices) { if (cancelled) { // we got cancelled - presumably a different user now return; } - var devices = client.getStoredDevicesForUser(member.userId); self._disambiguateDevices(devices); self.setState({devicesLoading: false, devices: devices}); }, function(err) {