diff --git a/src/DeviceListener.js b/src/DeviceListener.js index a4c5785db4..32024d1d87 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -24,6 +24,9 @@ function toastKey(device) { return 'newsession_' + device.deviceId; } +const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; +const THIS_DEVICE_TOAST_KEY = 'setupencryption'; + export default class DeviceListener { static sharedInstance() { if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener(); @@ -33,42 +36,114 @@ export default class DeviceListener { constructor() { // device IDs for which the user has dismissed the verify toast ('Later') this._dismissed = new Set(); + // has the user dismissed any of the various nag toasts to setup encryption on this device? + this._dismissedThisDeviceToast = false; + + // cache of the key backup info + this._keyBackupInfo = null; + this._keyBackupFetchedAt = null; } start() { MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated); MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged); - this.recheck(); + MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); + this._recheck(); } stop() { if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated); MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged); + MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged); } this._dismissed.clear(); } dismissVerification(deviceId) { this._dismissed.add(deviceId); - this.recheck(); + this._recheck(); + } + + dismissEncryptionSetup() { + this._dismissedThisDeviceToast = true; + this._recheck(); } _onDevicesUpdated = (users) => { if (!users.includes(MatrixClientPeg.get().getUserId())) return; - this.recheck(); + this._recheck(); } _onDeviceVerificationChanged = (users) => { if (!users.includes(MatrixClientPeg.get().getUserId())) return; - this.recheck(); + this._recheck(); } - async recheck() { + _onUserTrustStatusChanged = (userId, trustLevel) => { + if (userId !== MatrixClientPeg.get().getUserId()) return; + this._recheck(); + } + + // The server doesn't tell us when key backup is set up, so we poll + // & cache the result + async _getKeyBackupInfo() { + const now = (new Date()).getTime(); + if (!this._keyBackupInfo || this._keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) { + this._keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); + this._keyBackupFetchedAt = now; + } + return this._keyBackupInfo; + } + + async _recheck() { if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) return; const cli = MatrixClientPeg.get(); - if (!cli.isCryptoEnabled()) return false; + if (!cli.isCryptoEnabled()) return; + if (!cli.getCrossSigningId()) { + if (this._dismissedThisDeviceToast) { + ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); + return; + } + + // cross signing isn't enabled - nag to enable it + // There are 3 different toasts for: + if (cli.getStoredCrossSigningForUser(cli.getUserId())) { + // Cross-signing on account but this device doesn't trust the master key (verify this session) + ToastStore.sharedInstance().addOrReplaceToast({ + key: THIS_DEVICE_TOAST_KEY, + title: _t("Verify this session"), + icon: "verification_warning", + props: {kind: 'verify_this_session'}, + component: sdk.getComponent("toasts.SetupEncryptionToast"), + }); + } else { + const backupInfo = await this._getKeyBackupInfo(); + if (backupInfo) { + // No cross-signing on account but key backup available (upgrade encryption) + ToastStore.sharedInstance().addOrReplaceToast({ + key: THIS_DEVICE_TOAST_KEY, + title: _t("Encryption upgrade available"), + icon: "verification_warning", + props: {kind: 'upgrade_encryption'}, + component: sdk.getComponent("toasts.SetupEncryptionToast"), + }); + } else { + // No cross-signing or key backup on account (set up encryption) + ToastStore.sharedInstance().addOrReplaceToast({ + key: THIS_DEVICE_TOAST_KEY, + title: _t("Set up encryption"), + icon: "verification_warning", + props: {kind: 'set_up_encryption'}, + component: sdk.getComponent("toasts.SetupEncryptionToast"), + }); + } + } + return; + } else { + ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); + } const devices = await cli.getStoredDevicesForUser(cli.getUserId()); for (const device of devices) { diff --git a/src/components/views/toasts/NewSessionToast.js b/src/components/views/toasts/NewSessionToast.js index f83326121b..3b60f59131 100644 --- a/src/components/views/toasts/NewSessionToast.js +++ b/src/components/views/toasts/NewSessionToast.js @@ -32,7 +32,7 @@ export default class VerifySessionToast extends React.PureComponent { DeviceListener.sharedInstance().dismissVerification(this.props.deviceId); }; - _onVerifyClick = async () => { + _onReviewClick = async () => { const cli = MatrixClientPeg.get(); const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); @@ -47,10 +47,10 @@ export default class VerifySessionToast extends React.PureComponent { render() { const FormButton = sdk.getComponent("elements.FormButton"); return (