diff --git a/res/css/_components.scss b/res/css/_components.scss index 039bcd545b..083071ef6c 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/_RestoreKeyBackupDialog.scss"; @import "./views/directory/_NetworkDropdown.scss"; @import "./views/elements/_AccessibleButton.scss"; @import "./views/elements/_AddressSelector.scss"; diff --git a/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss b/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss new file mode 100644 index 0000000000..612c921038 --- /dev/null +++ b/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss @@ -0,0 +1,29 @@ +/* +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_RestoreKeyBackupDialog_primaryContainer { + /*FIXME: plinth colour in new theme(s). background-color: $accent-color;*/ + padding: 20px +} + +.mx_RestoreKeyBackupDialog_passPhraseInput, +.mx_RestoreKeyBackupDialog_recoveryKeyInput { + width: 300px; + border: 1px solid $accent-color; + border-radius: 5px; + padding: 10px; +} + diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index 9e5e61cb1a..e4250814d0 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -17,6 +17,7 @@ limitations under the License. import React from 'react'; import sdk from '../../../../index'; import MatrixClientPeg from '../../../../MatrixClientPeg'; +import Modal from '../../../../Modal'; import { _t } from '../../../../languageHandler'; @@ -33,6 +34,9 @@ export default React.createClass({ recoveryKey: "", recoverInfo: null, recoveryKeyValid: false, + forceRecoveryKey: false, + passPhrase: '', + recoveryKey: '', }; }, @@ -48,6 +52,18 @@ export default React.createClass({ this.props.onFinished(true); }, + _onUseRecoveryKeyClick: function() { + this.setState({ + forceRecoveryKey: true, + }); + }, + + _onResetRecoveryClick: function() { + this.props.onFinished(false); + const CreateKeyBackupDialog = sdk.getComponent("dialogs.keybackup.CreateKeyBackupDialog"); + Modal.createTrackedDialog('Create Key Backup', '', CreateKeyBackupDialog, {}); + }, + _onRecoveryKeyChange: function(e) { this.setState({ recoveryKey: e.target.value, @@ -55,13 +71,35 @@ export default React.createClass({ }); }, - _onRecoverClick: async function() { + _onPassPhraseNext: async function() { this.setState({ loading: true, restoreError: null, }); try { - const recoverInfo = await MatrixClientPeg.get().restoreKeyBackups( + const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword( + this.state.passPhrase, 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, + }); + } + }, + + _onRecoveryKeyNext: async function() { + this.setState({ + loading: true, + restoreError: null, + }); + try { + const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey( this.state.recoveryKey, undefined, undefined, this.state.backupInfo.version, ); this.setState({ @@ -77,6 +115,24 @@ export default React.createClass({ } }, + _onPassPhraseChange: function(e) { + this.setState({ + passPhrase: e.target.value, + }); + }, + + _onPassPhraseKeyPress: function(e) { + if (e.key === "Enter") { + this._onPassPhraseNext(); + } + }, + + _onRecoveryKeyKeyPress: function(e) { + if (e.key === "Enter" && this.state.recoveryKeyValid) { + this._onRecoveryKeyNext(); + } + }, + _loadBackupStatus: async function() { this.setState({ loading: true, @@ -102,16 +158,29 @@ export default React.createClass({ const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const Spinner = sdk.getComponent("elements.Spinner"); + const backupHasPassphrase = ( + this.state.backupInfo && + this.state.backupInfo.auth_data && + this.state.backupInfo.auth_data.private_key_salt && + this.state.backupInfo.auth_data.private_key_iterations + ); + let content; + let title; if (this.state.loading) { + title = _t("Loading..."); content = ; } else if (this.state.loadError) { + title = _t("Error"); content = _t("Unable to load backup status"); } else if (this.state.restoreError) { + title = _t("Error"); content = _t("Unable to restore backup"); } else if (this.state.backupInfo === null) { + title = _t("Error"); content = _t("No backup found!"); } else if (this.state.recoverInfo) { + title = _t("Backup Restored"); let failedToDecrypt; if (this.state.recoverInfo.total > this.state.recoverInfo.imported) { failedToDecrypt =

{_t( @@ -123,8 +192,54 @@ export default React.createClass({

{_t("Restored %(sessionCount)s session keys", {sessionCount: this.state.recoverInfo.imported})}

{failedToDecrypt} ; - } else { + } else if (backupHasPassphrase && !this.state.forceRecoveryKey) { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + title = _t("Enter Recovery Passphrase"); + content =
+ {_t( + "Access your secure message history and set up secure " + + "messaging by entering your recovery passphrase.", + )}
+ +
+ + +
+ {_t( + "If you've forgotten your recovery passphrase you can "+ + "use your recovery key or " + + "set up new recovery options" + , {}, { + button1: s => + {s} + , + button2: s => + {s} + , + })} +
; + } else { + title = _t("Enter Recovery Key"); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); let keyStatus; if (this.state.recoveryKey.length === 0) { @@ -140,28 +255,45 @@ export default React.createClass({ } content =
- {_t("Please enter the recovery key generated when you set up key backup")}
-