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:
parent
2b14f2af5c
commit
acc2e98355
9 changed files with 170 additions and 6 deletions
|
@ -34,7 +34,7 @@ body {
|
||||||
-webkit-font-smoothing: subpixel-antialiased;
|
-webkit-font-smoothing: subpixel-antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.error, div.warning {
|
.error, .warning {
|
||||||
color: $warning-color;
|
color: $warning-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
@import "./views/dialogs/_ShareDialog.scss";
|
@import "./views/dialogs/_ShareDialog.scss";
|
||||||
@import "./views/dialogs/_UnknownDeviceDialog.scss";
|
@import "./views/dialogs/_UnknownDeviceDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
|
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
|
||||||
|
@import "./views/dialogs/keybackup/_NewRecoveryMethodDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss";
|
@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss";
|
||||||
@import "./views/directory/_NetworkDropdown.scss";
|
@import "./views/directory/_NetworkDropdown.scss";
|
||||||
@import "./views/elements/_AccessibleButton.scss";
|
@import "./views/elements/_AccessibleButton.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;
|
||||||
|
}
|
1
res/img/e2e/lock-warning.svg
Normal file
1
res/img/e2e/lock-warning.svg
Normal 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 |
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1430,6 +1430,11 @@ export default React.createClass({
|
||||||
break;
|
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
|
// 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
|
// A later sync can/will correct the tint to be the right value for the user
|
||||||
|
|
|
@ -57,8 +57,7 @@ export default React.createClass({
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
|
||||||
// Title for the dialog.
|
// Title for the dialog.
|
||||||
// (could probably actually be something more complicated than a string if desired)
|
title: PropTypes.node.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
|
|
||||||
// children should be the content of the dialog
|
// children should be the content of the dialog
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
|
|
@ -154,6 +154,7 @@ export default class KeyBackupPanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => {
|
let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => {
|
||||||
|
const deviceName = sig.device.getDisplayName() || sig.device.deviceId;
|
||||||
const sigStatusSubstitutions = {
|
const sigStatusSubstitutions = {
|
||||||
validity: sub =>
|
validity: sub =>
|
||||||
<span className={sig.valid ? 'mx_KeyBackupPanel_sigValid' : 'mx_KeyBackupPanel_sigInvalid'}>
|
<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'}>
|
<span className={sig.device.isVerified() ? 'mx_KeyBackupPanel_deviceVerified' : 'mx_KeyBackupPanel_deviceNotVerified'}>
|
||||||
{sub}
|
{sub}
|
||||||
</span>,
|
</span>,
|
||||||
device: sub => <span className="mx_KeyBackupPanel_deviceName">{sig.device.getDisplayName()}</span>,
|
device: sub => <span className="mx_KeyBackupPanel_deviceName">{deviceName}</span>,
|
||||||
};
|
};
|
||||||
let sigStatus;
|
let sigStatus;
|
||||||
if (sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key()) {
|
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()) {
|
} else if (sig.valid && sig.device.isVerified()) {
|
||||||
sigStatus = _t(
|
sigStatus = _t(
|
||||||
"Backup has a <validity>valid</validity> signature from " +
|
"Backup has a <validity>valid</validity> signature from " +
|
||||||
"<verify>verified</verify> device <device>x</device>",
|
"<verify>verified</verify> device <device></device>",
|
||||||
{}, sigStatusSubstitutions,
|
{}, sigStatusSubstitutions,
|
||||||
);
|
);
|
||||||
} else if (sig.valid && !sig.device.isVerified()) {
|
} else if (sig.valid && !sig.device.isVerified()) {
|
||||||
|
|
|
@ -351,7 +351,7 @@
|
||||||
"This device is uploading keys to this backup": "This device is uploading keys to this backup",
|
"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",
|
"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 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 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>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>",
|
"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",
|
"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.",
|
"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.",
|
"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 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 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"
|
"Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room"
|
||||||
|
|
Loading…
Reference in a new issue