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 <jryans@gmail.com>
This commit is contained in:
J. Ryan Stinnett 2018-12-13 15:55:48 +00:00
parent 2b14f2af5c
commit acc2e98355
9 changed files with 170 additions and 6 deletions

View file

@ -34,7 +34,7 @@ body {
-webkit-font-smoothing: subpixel-antialiased;
}
div.error, div.warning {
.error, .warning {
color: $warning-color;
}

View file

@ -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";

View file

@ -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;
}

View file

@ -0,0 +1 @@
<svg height="42" width="37" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><mask id="a" fill="#fff"><path d="m23.521 14.596h-1.777a.454.454 0 0 1 -.456-.45v-4.14a8.974 8.974 0 0 0 -8.57-9 8.884 8.884 0 0 0 -9.253 8.82v4.365a.454.454 0 0 1 -.456.45h-1.78a1.218 1.218 0 0 0 -1.229 1.215v15.93a1.218 1.218 0 0 0 1.229 1.214h22.247a1.218 1.218 0 0 0 1.231-1.215v-15.974a1.153 1.153 0 0 0 -1.186-1.215zm-17.276-4.77a6.114 6.114 0 0 1 6.473-6.075 6.251 6.251 0 0 1 5.88 6.255v4.185a.454.454 0 0 1 -.456.45h-11.486a.454.454 0 0 1 -.456-.45v-4.365zm20.255 11.174c6.344.019 11.481 5.156 11.5 11.5 0 6.351-5.149 11.5-11.5 11.5s-11.5-5.149-11.5-11.5 5.149-11.5 11.5-11.5z" fill="#fff" fill-rule="evenodd"/></mask><g fill="#000" fill-rule="evenodd"><path d="m-.909 32.909h19.773c2.392-6.604 4.34-10.526 5.844-11.766s1.808-8.258.912-21.052h-26.529z" mask="url(#a)" transform="translate(0 -1)"/><path d="m26.5 21c-5.799 0-10.5 4.701-10.5 10.5s4.701 10.5 10.5 10.5 10.5-4.701 10.5-10.5c-.017-5.792-4.708-10.483-10.5-10.5zm1.444 16.012h-2.888v-2.493h3.019v2.494zm.131-9.712-.787 5.775h-1.575l-.788-5.775v-1.312h3.15z" fill-rule="nonzero"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -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 = <span className="mx_NewRecoveryMethodDialog_title">
{_t("New Recovery Method")}
</span>;
return (
<BaseDialog className="mx_NewRecoveryMethodDialog"
onFinished={this.props.onFinished}
title={title}
hasCancel={false}
>
<div>
<p>{_t(
"A new recovery passphrase and key for Secure " +
"Messages has been detected.",
)}</p>
<p>{_t(
"Setting up Secure Messages on this device " +
"will re-encrypt this device's message history with " +
"the new recovery method.",
)}</p>
<p className="warning">{_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.",
)}</p>
<DialogButtons
primaryButton={_t("Set up Secure Messages")}
onPrimaryButtonClick={this.onSetupClick}
cancelButton={_t("Go to Settings")}
onCancel={this.onGoToSettingsClick}
/>
</div>
</BaseDialog>
);
}
}

View file

@ -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

View file

@ -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,

View file

@ -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 =>
<span className={sig.valid ? 'mx_KeyBackupPanel_sigValid' : 'mx_KeyBackupPanel_sigInvalid'}>
@ -163,7 +164,7 @@ export default class KeyBackupPanel extends React.Component {
<span className={sig.device.isVerified() ? 'mx_KeyBackupPanel_deviceVerified' : 'mx_KeyBackupPanel_deviceNotVerified'}>
{sub}
</span>,
device: sub => <span className="mx_KeyBackupPanel_deviceName">{sig.device.getDisplayName()}</span>,
device: sub => <span className="mx_KeyBackupPanel_deviceName">{deviceName}</span>,
};
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 <validity>valid</validity> signature from " +
"<verify>verified</verify> device <device>x</device>",
"<verify>verified</verify> device <device></device>",
{}, sigStatusSubstitutions,
);
} else if (sig.valid && !sig.device.isVerified()) {

View file

@ -351,7 +351,7 @@
"This device is uploading keys to this backup": "This device is uploading keys to this backup",
"This device is <b>not</b> uploading keys to this backup": "This device is <b>not</b> uploading keys to this backup",
"Backup has a <validity>valid</validity> signature from this device": "Backup has a <validity>valid</validity> signature from this device",
"Backup has a <validity>valid</validity> signature from <verify>verified</verify> device <device>x</device>": "Backup has a <validity>valid</validity> signature from <verify>verified</verify> device <device>x</device>",
"Backup has a <validity>valid</validity> signature from <verify>verified</verify> device <device></device>": "Backup has a <validity>valid</validity> signature from <verify>verified</verify> device <device></device>",
"Backup has a <validity>valid</validity> signature from <verify>unverified</verify> device <device></device>": "Backup has a <validity>valid</validity> signature from <verify>unverified</verify> device <device></device>",
"Backup has an <validity>invalid</validity> signature from <verify>verified</verify> device <device></device>": "Backup has an <validity>invalid</validity> signature from <verify>verified</verify> device <device></device>",
"Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> device <device></device>": "Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> device <device></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"