Support restoring key backup

pull/21833/head
David Baker 2018-09-17 16:00:23 +01:00
parent 2e6d27717c
commit 9a65e6817a
3 changed files with 180 additions and 2 deletions

View File

@ -0,0 +1,156 @@
/*
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 Modal from '../../../../Modal';
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../../index';
import MatrixClientPeg from '../../../../MatrixClientPeg';
import { formatCryptoKey } from '../../../../utils/FormattingUtils';
import Promise from 'bluebird';
import { _t, _td } from '../../../../languageHandler';
/**
* Dialog for restoring e2e keys from a backup and the user's recovery key
*/
export default React.createClass({
getInitialState: function() {
return {
backupInfo: null,
loading: false,
loadError: null,
restoreError: null,
recoveryKey: "",
recoverInfo: null,
};
},
componentWillMount: function() {
this._loadBackupStatus();
},
_onCancel: function() {
this.props.onFinished(false);
},
_onDone: function() {
this.props.onFinished(true);
},
_onRecoveryKeyChange: function(e) {
this.setState({recoveryKey: e.target.value});
},
_onRecoverClick: async function() {
this.setState({
loading: true,
restoreError: null,
});
try {
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackups(
this.state.recoveryKey.replace(/ /g, ''), undefined, undefined, this.state.backupInfo.version,
);
this.setState({
loading: false,
recoverInfo,
});
} catch (e) {
console.log("Error restoring backup", e);
this.setState({
loading: false,
restoreError: e,
});
}
},
_loadBackupStatus: async function() {
this.setState({
loading: true,
loadError: null,
});
try {
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
this.setState({
loadError: null,
loading: false,
backupInfo,
});
} catch (e) {
console.log("Error loading backup status", e);
this.setState({
loadError: e,
loading: false,
});
}
},
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const Spinner = sdk.getComponent("elements.Spinner");
let content;
if (this.state.loading) {
content = <Spinner />;
} else if (this.state.loadError) {
content = _t("Unable to load backup status");
} else if (this.state.restoreError) {
content = _t("Unable to restore backup");
} else if (this.state.backupInfo === null) {
content = _t("No backup found!");
} else if (this.state.recoverInfo) {
let failedToDecrypt;
if (this.state.recoverInfo.total > this.state.recoverInfo.imported) {
failedToDecrypt = <p>{_t(
"Failed to decrypt %(failedCount)s sessions!",
{failedCount: this.state.recoverInfo.total - this.state.recoverInfo.imported},
)}</p>;
}
content = <div>
<p>{_t("Restored %(sessionCount)s session keys", {sessionCount: this.state.recoverInfo.imported})}</p>
{failedToDecrypt}
</div>;
} else {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
content = <div>
{_t("Please enter the recovery key generated when you set up key backup")}<br />
<textarea
onChange={this._onRecoveryKeyChange}
value={this.state.recoveryKey}
style={{width: "90%"}}
autoFocus={true}
/>
<DialogButtons primaryButton={_t('Recover')}
onPrimaryButtonClick={this._onRecoverClick}
hasCancel={true}
onCancel={this._onCancel}
focus={false}
/>
</div>;
}
return (
<BaseDialog className='mx_RestoreKeyBackupDialog'
onFinished={this.props.onFinished}
title={_t('Restore Key Backup')}
>
<div>
{content}
</div>
</BaseDialog>
);
},
});

View File

@ -29,6 +29,7 @@ export default class KeyBackupPanel extends React.Component {
this._deleteBackup = this._deleteBackup.bind(this);
this._verifyDevice = this._verifyDevice.bind(this);
this._onKeyBackupStatus = this._onKeyBackupStatus.bind(this);
this._restoreBackup = this._restoreBackup.bind(this);
this._unmounted = false;
this.state = {
@ -47,8 +48,10 @@ export default class KeyBackupPanel extends React.Component {
componentWillUnmount() {
this._unmounted = true;
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('keyBackupStatus', this._onKeyBackupStatus);
}
}
_onKeyBackupStatus() {
this._loadBackupStatus();
@ -105,6 +108,12 @@ export default class KeyBackupPanel extends React.Component {
});
}
_restoreBackup() {
const RestoreKeyBackupDialog = sdk.getComponent("dialogs.keybackup.RestoreKeyBackupDialog");
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
});
}
_verifyDevice(e) {
const device = this.state.backupSigStatus.sigs[e.target.getAttribute('data-sigindex')].device;
@ -204,6 +213,10 @@ export default class KeyBackupPanel extends React.Component {
{clientBackupStatus}<br />
<div>{backupSigStatuses}</div><br />
<br />
<AccessibleButton className="mx_UserSettings_button"
onClick={this._restoreBackup}>
{ _t("Restore backup") }
</AccessibleButton>&nbsp;&nbsp;&nbsp;
<AccessibleButton className="mx_UserSettings_button danger"
onClick={this._deleteBackup}>
{ _t("Delete backup") }

View File

@ -308,9 +308,11 @@
"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>",
"Verify...": "Verify...",
"Backup is not signed by any of your devices": "Backup is not signed by any of your devices",
"Backup version: ": "Backup version: ",
"Algorithm: ": "Algorithm: ",
"Restore backup": "Restore backup",
"No backup is present": "No backup is present",
"Start a new backup": "Start a new backup",
"Error saving email notification preferences": "Error saving email notification preferences",
@ -742,7 +744,6 @@
"Unblacklist": "Unblacklist",
"Blacklist": "Blacklist",
"Unverify": "Unverify",
"Verify...": "Verify...",
"No results": "No results",
"Delete": "Delete",
"Communities": "Communities",
@ -966,6 +967,14 @@
"Creating backup...": "Creating backup...",
"Uploading keys...": "Uploading keys...",
"Create Key Backup": "Create Key Backup",
"Unable to load backup status": "Unable to load backup status",
"Unable to restore backup": "Unable to restore backup",
"No backup found!": "No backup found!",
"Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!",
"Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys",
"Please enter the recovery key generated when you set up key backup": "Please enter the recovery key generated when you set up key backup",
"Recover": "Recover",
"Restore Key Backup": "Restore Key Backup",
"Private Chat": "Private Chat",
"Public Chat": "Public Chat",
"Custom": "Custom",