Merge pull request #3940 from matrix-org/dbkr/e2e_upgrade_toast

Verification nag toasts
This commit is contained in:
David Baker 2020-01-27 10:16:18 +00:00 committed by GitHub
commit d5ac37a4ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 160 additions and 12 deletions

View file

@ -24,6 +24,9 @@ function toastKey(device) {
return 'newsession_' + device.deviceId; return 'newsession_' + device.deviceId;
} }
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
const THIS_DEVICE_TOAST_KEY = 'setupencryption';
export default class DeviceListener { export default class DeviceListener {
static sharedInstance() { static sharedInstance() {
if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener(); if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener();
@ -33,42 +36,114 @@ export default class DeviceListener {
constructor() { constructor() {
// device IDs for which the user has dismissed the verify toast ('Later') // device IDs for which the user has dismissed the verify toast ('Later')
this._dismissed = new Set(); 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() { start() {
MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated); MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated);
MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged); MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged);
this.recheck(); MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
this._recheck();
} }
stop() { stop() {
if (MatrixClientPeg.get()) { if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated); MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated);
MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged); MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged);
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
} }
this._dismissed.clear(); this._dismissed.clear();
} }
dismissVerification(deviceId) { dismissVerification(deviceId) {
this._dismissed.add(deviceId); this._dismissed.add(deviceId);
this.recheck(); this._recheck();
}
dismissEncryptionSetup() {
this._dismissedThisDeviceToast = true;
this._recheck();
} }
_onDevicesUpdated = (users) => { _onDevicesUpdated = (users) => {
if (!users.includes(MatrixClientPeg.get().getUserId())) return; if (!users.includes(MatrixClientPeg.get().getUserId())) return;
this.recheck(); this._recheck();
} }
_onDeviceVerificationChanged = (users) => { _onDeviceVerificationChanged = (users) => {
if (!users.includes(MatrixClientPeg.get().getUserId())) return; 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; if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) return;
const cli = MatrixClientPeg.get(); 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()); const devices = await cli.getStoredDevicesForUser(cli.getUserId());
for (const device of devices) { for (const device of devices) {

View file

@ -32,7 +32,7 @@ export default class VerifySessionToast extends React.PureComponent {
DeviceListener.sharedInstance().dismissVerification(this.props.deviceId); DeviceListener.sharedInstance().dismissVerification(this.props.deviceId);
}; };
_onVerifyClick = async () => { _onReviewClick = async () => {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
@ -47,10 +47,10 @@ export default class VerifySessionToast extends React.PureComponent {
render() { render() {
const FormButton = sdk.getComponent("elements.FormButton"); const FormButton = sdk.getComponent("elements.FormButton");
return (<div> return (<div>
<div className="mx_Toast_description">{_t("Other users may not trust it")}</div> <div className="mx_Toast_description">{_t("Review & verify your new session")}</div>
<div className="mx_Toast_buttons" aria-live="off"> <div className="mx_Toast_buttons" aria-live="off">
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} /> <FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
<FormButton label={_t("Verify")} onClick={this._onVerifyClick} /> <FormButton label={_t("Review")} onClick={this._onReviewClick} />
</div> </div>
</div>); </div>);
} }

View file

@ -0,0 +1,68 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
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 * as sdk from "../../../index";
import { _t } from '../../../languageHandler';
import DeviceListener from '../../../DeviceListener';
import { accessSecretStorage } from '../../../CrossSigningManager';
export default class SetupEncryptionToast extends React.PureComponent {
static propTypes = {
toastKey: PropTypes.string.isRequired,
kind: PropTypes.oneOf(['set_up_encryption', 'verify_this_session', 'upgrade_encryption']).isRequired,
};
_onLaterClick = () => {
DeviceListener.sharedInstance().dismissEncryptionSetup();
};
_onSetupClick = async () => {
accessSecretStorage();
};
getDescription() {
switch (this.props.kind) {
case 'set_up_encryption':
case 'upgrade_encryption':
return _t('Verify your other devices easier');
case 'verify_this_session':
return _t('Other users may not trust it');
}
}
getSetupCaption() {
switch (this.props.kind) {
case 'set_up_encryption':
case 'upgrade_encryption':
return _t('Upgrade');
case 'verify_this_session':
return _t('Verify');
}
}
render() {
const FormButton = sdk.getComponent("elements.FormButton");
return (<div>
<div className="mx_Toast_description">{this.getDescription()}</div>
<div className="mx_Toast_buttons" aria-live="off">
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
<FormButton label={this.getSetupCaption()} onClick={this._onSetupClick} />
</div>
</div>);
}
}

View file

@ -88,6 +88,9 @@
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
"Verify this session": "Verify this session",
"Encryption upgrade available": "Encryption upgrade available",
"Set up encryption": "Set up encryption",
"New Session": "New Session", "New Session": "New Session",
"Who would you like to add to this community?": "Who would you like to add to this community?", "Who would you like to add to this community?": "Who would you like to add to this community?",
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID",
@ -509,8 +512,12 @@
"Headphones": "Headphones", "Headphones": "Headphones",
"Folder": "Folder", "Folder": "Folder",
"Pin": "Pin", "Pin": "Pin",
"Other users may not trust it": "Other users may not trust it", "Review & verify your new session": "Review & verify your new session",
"Later": "Later", "Later": "Later",
"Review": "Review",
"Verify your other devices easier": "Verify your other devices easier",
"Other users may not trust it": "Other users may not trust it",
"Upgrade": "Upgrade",
"Verify": "Verify", "Verify": "Verify",
"Decline (%(counter)s)": "Decline (%(counter)s)", "Decline (%(counter)s)": "Decline (%(counter)s)",
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:", "Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
@ -1514,7 +1521,6 @@
"Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.", "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.",
"This usually only affects how the room is processed on the server. If you're having problems with your Riot, please <a>report a bug</a>.": "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please <a>report a bug</a>.", "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please <a>report a bug</a>.": "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please <a>report a bug</a>.",
"You'll upgrade this room from <oldVersion /> to <newVersion />.": "You'll upgrade this room from <oldVersion /> to <newVersion />.", "You'll upgrade this room from <oldVersion /> to <newVersion />.": "You'll upgrade this room from <oldVersion /> to <newVersion />.",
"Upgrade": "Upgrade",
"Sign out and remove encryption keys?": "Sign out and remove encryption keys?", "Sign out and remove encryption keys?": "Sign out and remove encryption keys?",
"Clear Storage and Sign Out": "Clear Storage and Sign Out", "Clear Storage and Sign Out": "Clear Storage and Sign Out",
"Send Logs": "Send Logs", "Send Logs": "Send Logs",
@ -2008,7 +2014,6 @@
"Set up secret storage": "Set up secret storage", "Set up secret storage": "Set up secret storage",
"Restore your Key Backup": "Restore your Key Backup", "Restore your Key Backup": "Restore your Key Backup",
"Upgrade your encryption": "Upgrade your encryption", "Upgrade your encryption": "Upgrade your encryption",
"Set up encryption": "Set up encryption",
"Recovery key": "Recovery key", "Recovery key": "Recovery key",
"Keep it safe": "Keep it safe", "Keep it safe": "Keep it safe",
"Storing secrets...": "Storing secrets...", "Storing secrets...": "Storing secrets...",