diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 0e0d56647d..dbfd6a2775 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -161,6 +161,7 @@ module.exports = React.createClass({ MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember); MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership); MatrixClientPeg.get().on("accountData", this.onAccountData); + MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus); this._fetchMediaConfig(); // Start listening for RoomViewStore updates this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); @@ -449,6 +450,7 @@ module.exports = React.createClass({ MatrixClientPeg.get().removeListener("Room.myMembership", this.onMyMembership); MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); MatrixClientPeg.get().removeListener("accountData", this.onAccountData); + MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -618,6 +620,11 @@ module.exports = React.createClass({ false, ); } + }, + + onKeyBackupStatus() { + // Key backup status changes affect whether the in-room recovery + // reminder is displayed. this.forceUpdate(); }, diff --git a/src/components/views/rooms/RoomRecoveryReminder.js b/src/components/views/rooms/RoomRecoveryReminder.js index 265bfd3ee3..d03c5fc96d 100644 --- a/src/components/views/rooms/RoomRecoveryReminder.js +++ b/src/components/views/rooms/RoomRecoveryReminder.js @@ -19,13 +19,76 @@ import PropTypes from "prop-types"; import sdk from "../../../index"; import { _t } from "../../../languageHandler"; import Modal from "../../../Modal"; +import MatrixClientPeg from "../../../MatrixClientPeg"; export default class RoomRecoveryReminder extends React.PureComponent { static propTypes = { onFinished: PropTypes.func.isRequired, } - showKeyBackupDialog = () => { + constructor(props) { + super(props); + + this.state = { + loading: true, + error: null, + unverifiedDevice: null, + }; + } + + componentWillMount() { + this._loadBackupStatus(); + } + + async _loadBackupStatus() { + 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); + this.setState({ + loading: false, + error: e, + }); + return; + } + + let unverifiedDevice; + for (const sig of backupSigStatus.sigs) { + if (!sig.device.isVerified()) { + unverifiedDevice = sig.device; + break; + } + } + this.setState({ + loading: false, + unverifiedDevice, + }); + } + + showSetupDialog = () => { + if (this.state.unverifiedDevice) { + // A key backup exists for this account, but the creating device is not + // verified, so we'll show the device verify dialog. + // 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 (for cases where a key backup exists but the backup creating + // device is unverified). 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. + const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); + Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, { + userId: MatrixClientPeg.get().credentials.userId, + device: this.state.unverifiedDevice, + onFinished: this.props.onFinished, + }); + return; + } + + // The default case assumes that a key backup doesn't exist for this account, so + // we'll show the create key backup flow. Modal.createTrackedDialogAsync("Key Backup", "Key Backup", import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"), { @@ -46,29 +109,51 @@ export default class RoomRecoveryReminder extends React.PureComponent { this.props.onFinished(false); }, onSetup: () => { - this.showKeyBackupDialog(); + this.showSetupDialog(); }, }, ); } onSetupClick = () => { - this.showKeyBackupDialog(); + this.showSetupDialog(); } render() { + if (this.state.loading) { + return null; + } + const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton"); + let body; + if (this.state.error) { + body =