Secure backup prompt

This commit is contained in:
David Baker 2024-12-02 14:44:00 +00:00
parent cf52974e09
commit b8f6bd1664
3 changed files with 56 additions and 13 deletions

View file

@ -292,13 +292,20 @@ export default class DeviceListener {
await crypto.getUserDeviceInfo([cli.getSafeUserId()]); await crypto.getUserDeviceInfo([cli.getSafeUserId()]);
// cross signing isn't enabled - nag to enable it // cross signing isn't enabled - nag to enable it
// There are 2 different toasts for: // There are 3 different toasts for:
if (!(await crypto.getCrossSigningKeyId()) && (await crypto.userHasCrossSigningKeys())) { if (!(await crypto.getCrossSigningKeyId()) && (await crypto.userHasCrossSigningKeys())) {
// Cross-signing on account but this device doesn't trust the master key (verify this session) // Toast 1. Cross-signing on account but this device doesn't trust the master key (verify this session)
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION); showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
this.checkKeyBackupStatus(); this.checkKeyBackupStatus();
} else { } else {
// No cross-signing or key backup on account (set up encryption) const backupInfo = await this.getKeyBackupInfo();
if (backupInfo) {
// Toast 2: Key backup is enabled but recovery (4S) is not set up: prompt user to set up recovery.
// Since we now enable key backup at registration time, this will be the common case for
// new users.
showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY);
} else {
// Toast 3: No cross-signing or key backup on account (set up encryption)
await cli.waitForClientWellKnown(); await cli.waitForClientWellKnown();
if (isSecureBackupRequired(cli) && isLoggedIn()) { if (isSecureBackupRequired(cli) && isLoggedIn()) {
// If we're meant to set up, and Secure Backup is required, // If we're meant to set up, and Secure Backup is required,
@ -310,6 +317,7 @@ export default class DeviceListener {
} }
} }
} }
}
// This needs to be done after awaiting on getUserDeviceInfo() above, so // This needs to be done after awaiting on getUserDeviceInfo() above, so
// we make sure we get the devices after the fetch is done. // we make sure we get the devices after the fetch is done.

View file

@ -912,6 +912,9 @@
"warning": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings." "warning": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings."
}, },
"reset_all_button": "Forgotten or lost all recovery methods? <a>Reset all</a>", "reset_all_button": "Forgotten or lost all recovery methods? <a>Reset all</a>",
"set_up_recovery": "Set up recovery",
"set_up_recovery_later": "Not now",
"set_up_recovery_toast_title": "Set up recovery to protect your account",
"set_up_toast_description": "Safeguard against losing access to encrypted messages & data", "set_up_toast_description": "Safeguard against losing access to encrypted messages & data",
"set_up_toast_title": "Set up Secure Backup", "set_up_toast_title": "Set up Secure Backup",
"setup_secure_backup": { "setup_secure_backup": {

View file

@ -6,6 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import KeyboardIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";
import { ComponentType } from "react";
import Modal from "../Modal"; import Modal from "../Modal";
import { _t } from "../languageHandler"; import { _t } from "../languageHandler";
import DeviceListener from "../DeviceListener"; import DeviceListener from "../DeviceListener";
@ -23,33 +26,61 @@ const getTitle = (kind: Kind): string => {
switch (kind) { switch (kind) {
case Kind.SET_UP_ENCRYPTION: case Kind.SET_UP_ENCRYPTION:
return _t("encryption|set_up_toast_title"); return _t("encryption|set_up_toast_title");
case Kind.SET_UP_RECOVERY:
return _t("encryption|set_up_recovery_toast_title");
case Kind.VERIFY_THIS_SESSION: case Kind.VERIFY_THIS_SESSION:
return _t("encryption|verify_toast_title"); return _t("encryption|verify_toast_title");
} }
}; };
const getIcon = (kind: Kind): string => { const getIcon = (kind: Kind): string | undefined => {
switch (kind) { switch (kind) {
case Kind.SET_UP_ENCRYPTION: case Kind.SET_UP_ENCRYPTION:
return "secure_backup"; return "secure_backup";
case Kind.SET_UP_RECOVERY:
return undefined;
case Kind.VERIFY_THIS_SESSION: case Kind.VERIFY_THIS_SESSION:
return "verification_warning"; return "verification_warning";
} }
}; };
// Gets the icon displayed on the prinary button
const getPrimaryIcon = (kind: Kind): ComponentType<React.SVGAttributes<SVGElement>> | undefined => {
switch (kind) {
case Kind.SET_UP_RECOVERY:
return KeyboardIcon;
default:
return undefined;
}
};
const getSetupCaption = (kind: Kind): string => { const getSetupCaption = (kind: Kind): string => {
switch (kind) { switch (kind) {
case Kind.SET_UP_ENCRYPTION: case Kind.SET_UP_ENCRYPTION:
return _t("action|continue"); return _t("action|continue");
case Kind.SET_UP_RECOVERY:
return _t("encryption|set_up_recovery");
case Kind.VERIFY_THIS_SESSION: case Kind.VERIFY_THIS_SESSION:
return _t("action|verify"); return _t("action|verify");
} }
}; };
const getSecondaryButtonLabel = (kind: Kind): string => {
switch (kind) {
case Kind.SET_UP_RECOVERY:
return _t("encryption|set_up_recovery_later");
case Kind.SET_UP_ENCRYPTION:
case Kind.VERIFY_THIS_SESSION:
return _t("encryption|verification|unverified_sessions_toast_reject");
}
};
const getDescription = (kind: Kind): string => { const getDescription = (kind: Kind): string => {
switch (kind) { switch (kind) {
case Kind.SET_UP_ENCRYPTION: case Kind.SET_UP_ENCRYPTION:
return _t("encryption|set_up_toast_description"); return _t("encryption|set_up_toast_description");
case Kind.SET_UP_RECOVERY:
return _t("encryption|set_up_recovery_toast_title");
case Kind.VERIFY_THIS_SESSION: case Kind.VERIFY_THIS_SESSION:
return _t("encryption|verify_toast_description"); return _t("encryption|verify_toast_description");
} }
@ -57,6 +88,7 @@ const getDescription = (kind: Kind): string => {
export enum Kind { export enum Kind {
SET_UP_ENCRYPTION = "set_up_encryption", SET_UP_ENCRYPTION = "set_up_encryption",
SET_UP_RECOVERY = "set_up_recovery",
VERIFY_THIS_SESSION = "verify_this_session", VERIFY_THIS_SESSION = "verify_this_session",
} }
@ -101,9 +133,9 @@ export const showToast = (kind: Kind): void => {
description: getDescription(kind), description: getDescription(kind),
primaryLabel: getSetupCaption(kind), primaryLabel: getSetupCaption(kind),
onPrimaryClick: onAccept, onPrimaryClick: onAccept,
secondaryLabel: _t("encryption|verification|unverified_sessions_toast_reject"), secondaryLabel: getSecondaryButtonLabel(kind),
onSecondaryClick: onReject, onSecondaryClick: onReject,
destructive: "secondary", PrimaryIcon: getPrimaryIcon(kind),
}, },
component: GenericToast, component: GenericToast,
priority: kind === Kind.VERIFY_THIS_SESSION ? 95 : 40, priority: kind === Kind.VERIFY_THIS_SESSION ? 95 : 40,