From acc2e98355fcde7b1cfee2b7e49bea088c02af5d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 13 Dec 2018 15:55:48 +0000 Subject: [PATCH] Add New Recovery Method dialog Adds a New Recovery Method dialog which is shown when key backup fails because of a version mismatch / version not found error. The set up button in the dialog currently only marks a device as verified (via a verification prompt) instead of the eventual restore and cross-sign flow, since those pieces don't exist yet. Signed-off-by: J. Ryan Stinnett --- res/css/_common.scss | 2 +- res/css/_components.scss | 1 + .../keybackup/_NewRecoveryMethodDialog.scss | 41 +++++++ res/img/e2e/lock-warning.svg | 1 + .../keybackup/NewRecoveryMethodDialog.js | 110 ++++++++++++++++++ src/components/structures/MatrixChat.js | 5 + src/components/views/dialogs/BaseDialog.js | 3 +- .../views/settings/KeyBackupPanel.js | 5 +- src/i18n/strings/en_EN.json | 8 +- 9 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 res/css/views/dialogs/keybackup/_NewRecoveryMethodDialog.scss create mode 100644 res/img/e2e/lock-warning.svg create mode 100644 src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js diff --git a/res/css/_common.scss b/res/css/_common.scss index 11e04f5dc0..97ae5412e1 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -34,7 +34,7 @@ body { -webkit-font-smoothing: subpixel-antialiased; } -div.error, div.warning { +.error, .warning { color: $warning-color; } diff --git a/res/css/_components.scss b/res/css/_components.scss index 579856f880..48aa211fd8 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -47,6 +47,7 @@ @import "./views/dialogs/_ShareDialog.scss"; @import "./views/dialogs/_UnknownDeviceDialog.scss"; @import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss"; +@import "./views/dialogs/keybackup/_NewRecoveryMethodDialog.scss"; @import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss"; @import "./views/directory/_NetworkDropdown.scss"; @import "./views/elements/_AccessibleButton.scss"; diff --git a/res/css/views/dialogs/keybackup/_NewRecoveryMethodDialog.scss b/res/css/views/dialogs/keybackup/_NewRecoveryMethodDialog.scss new file mode 100644 index 0000000000..370f82d9ab --- /dev/null +++ b/res/css/views/dialogs/keybackup/_NewRecoveryMethodDialog.scss @@ -0,0 +1,41 @@ +/* +Copyright 2018 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_NewRecoveryMethodDialog .mx_Dialog_title { + margin-bottom: 32px; +} + +.mx_NewRecoveryMethodDialog_title { + position: relative; + padding-left: 45px; + padding-bottom: 10px; + + &:before { + mask: url("../../../img/e2e/lock-warning.svg"); + mask-repeat: no-repeat; + background-color: $primary-fg-color; + content: ""; + position: absolute; + top: -6px; + right: 0; + bottom: 0; + left: 0; + } +} + +.mx_NewRecoveryMethodDialog .mx_Dialog_buttons { + margin-top: 36px; +} diff --git a/res/img/e2e/lock-warning.svg b/res/img/e2e/lock-warning.svg new file mode 100644 index 0000000000..a984ed85a0 --- /dev/null +++ b/res/img/e2e/lock-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js new file mode 100644 index 0000000000..e88e0444bc --- /dev/null +++ b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js @@ -0,0 +1,110 @@ +/* +Copyright 2018 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import PropTypes from "prop-types"; +import sdk from "../../../../index"; +import MatrixClientPeg from '../../../../MatrixClientPeg'; +import dis from "../../../../dispatcher"; +import { _t } from "../../../../languageHandler"; +import Modal from "../../../../Modal"; + +export default class NewRecoveryMethodDialog extends React.PureComponent { + static propTypes = { + onFinished: PropTypes.func.isRequired, + } + + onGoToSettingsClick = () => { + this.props.onFinished(); + dis.dispatch({ action: 'view_user_settings' }); + } + + onSetupClick = async() => { + // TODO: Should change to a restore key backup flow that checks the + // recovery passphrase while at the same time also cross-signing the + // device as well in a single flow. Since we don't have that yet, we'll + // look for an unverified device and verify it. Note that this means + // we won't restore keys yet; instead we'll only trust the backup for + // sending our own new keys to it. + let backupSigStatus; + try { + const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); + backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo); + } catch (e) { + console.log("Unable to fetch key backup status", e); + return; + } + + let unverifiedDevice; + for (const sig of backupSigStatus.sigs) { + if (!sig.device.isVerified()) { + unverifiedDevice = sig.device; + break; + } + } + if (!unverifiedDevice) { + console.log("Unable to find a device to verify."); + return; + } + + const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); + Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, { + userId: MatrixClientPeg.get().credentials.userId, + device: unverifiedDevice, + onFinished: this.props.onFinished, + }); + } + + render() { + const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); + const DialogButtons = sdk.getComponent("views.elements.DialogButtons"); + const title = + {_t("New Recovery Method")} + ; + + return ( + +
+

{_t( + "A new recovery passphrase and key for Secure " + + "Messages has been detected.", + )}

+

{_t( + "Setting up Secure Messages on this device " + + "will re-encrypt this device's message history with " + + "the new recovery method.", + )}

+

{_t( + "If you didn't set the new recovery method, an " + + "attacker may be trying to access your account. " + + "Change your account password and set a new recovery " + + "method immediately in Settings.", + )}

+ +
+
+ ); + } +} diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 4517304453..fd95276445 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1430,6 +1430,11 @@ export default React.createClass({ break; } }); + cli.on("crypto.keyBackupFailed", () => { + Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method', + import('../../async-components/views/dialogs/keybackup/NewRecoveryMethodDialog'), + ); + }); // Fire the tinter right on startup to ensure the default theme is applied // A later sync can/will correct the tint to be the right value for the user diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js index 8ec417a59b..3e9052cc34 100644 --- a/src/components/views/dialogs/BaseDialog.js +++ b/src/components/views/dialogs/BaseDialog.js @@ -57,8 +57,7 @@ export default React.createClass({ className: PropTypes.string, // Title for the dialog. - // (could probably actually be something more complicated than a string if desired) - title: PropTypes.string.isRequired, + title: PropTypes.node.isRequired, // children should be the content of the dialog children: PropTypes.node, diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index b08f4d0e78..03b98d28a0 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -154,6 +154,7 @@ export default class KeyBackupPanel extends React.Component { } let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => { + const deviceName = sig.device.getDisplayName() || sig.device.deviceId; const sigStatusSubstitutions = { validity: sub => @@ -163,7 +164,7 @@ export default class KeyBackupPanel extends React.Component { {sub} , - device: sub => {sig.device.getDisplayName()}, + device: sub => {deviceName}, }; let sigStatus; if (sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key()) { @@ -174,7 +175,7 @@ export default class KeyBackupPanel extends React.Component { } else if (sig.valid && sig.device.isVerified()) { sigStatus = _t( "Backup has a valid signature from " + - "verified device x", + "verified device ", {}, sigStatusSubstitutions, ); } else if (sig.valid && !sig.device.isVerified()) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 00f781ea5b..b5a5762cc4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -351,7 +351,7 @@ "This device is uploading keys to this backup": "This device is uploading keys to this backup", "This device is not uploading keys to this backup": "This device is not uploading keys to this backup", "Backup has a valid signature from this device": "Backup has a valid signature from this device", - "Backup has a valid signature from verified device x": "Backup has a valid signature from verified device x", + "Backup has a valid signature from verified device ": "Backup has a valid signature from verified device ", "Backup has a valid signature from unverified device ": "Backup has a valid signature from unverified device ", "Backup has an invalid signature from verified device ": "Backup has an invalid signature from verified device ", "Backup has an invalid signature from unverified device ": "Backup has an invalid signature from unverified device ", @@ -1401,6 +1401,12 @@ "Retry": "Retry", "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.", "If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.", + "New Recovery Method": "New Recovery Method", + "A new recovery passphrase and key for Secure Messages has been detected.": "A new recovery passphrase and key for Secure Messages has been detected.", + "Setting up Secure Messages on this device will re-encrypt this device's message history with the new recovery method.": "Setting up Secure Messages on this device will re-encrypt this device's message history with the new recovery method.", + "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", + "Set up Secure Messages": "Set up Secure Messages", + "Go to Settings": "Go to Settings", "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room"