diff --git a/res/css/_components.pcss b/res/css/_components.pcss index a114c998b8b..accc67f500e 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -331,8 +331,6 @@ @import "./views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss"; @import "./views/rooms/wysiwyg_composer/components/_LinkModal.pcss"; @import "./views/settings/_AvatarSetting.pcss"; -@import "./views/settings/_CrossSigningPanel.pcss"; -@import "./views/settings/_CryptographyPanel.pcss"; @import "./views/settings/_FontScalingPanel.pcss"; @import "./views/settings/_ImageSizePanel.pcss"; @import "./views/settings/_IntegrationManager.pcss"; @@ -345,7 +343,6 @@ @import "./views/settings/_PhoneNumbers.pcss"; @import "./views/settings/_PowerLevelSelector.pcss"; @import "./views/settings/_RoomProfileSettings.pcss"; -@import "./views/settings/_SecureBackupPanel.pcss"; @import "./views/settings/_SetIdServer.pcss"; @import "./views/settings/_SetIntegrationManager.pcss"; @import "./views/settings/_SettingsFieldset.pcss"; diff --git a/res/css/views/settings/_CrossSigningPanel.pcss b/res/css/views/settings/_CrossSigningPanel.pcss deleted file mode 100644 index e9b2aa0c293..00000000000 --- a/res/css/views/settings/_CrossSigningPanel.pcss +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2019 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_CrossSigningPanel_statusList { - border-spacing: 0; - - th { - text-align: start; - } - - td, - th { - padding: 0; - - &:first-of-type { - padding-inline-end: 1em; - } - } -} - -.mx_CrossSigningPanel_buttonRow { - margin: 1em 0; - - :nth-child(n + 1) { - margin-inline-end: 10px; - } -} - -.mx_CrossSigningPanel_advanced { - width: fit-content; -} diff --git a/res/css/views/settings/_CryptographyPanel.pcss b/res/css/views/settings/_CryptographyPanel.pcss deleted file mode 100644 index 9c174ceaab7..00000000000 --- a/res/css/views/settings/_CryptographyPanel.pcss +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_CryptographyPanel_sessionInfo { - padding: 0em; - border-spacing: 0px; -} -.mx_CryptographyPanel_sessionInfo > tr { - vertical-align: baseline; - padding: 0em; - - th { - text-align: start; - } - - td, - th { - padding: 0 1em 0 0; - } -} - -.mx_CryptographyPanel_importExportButtons { - display: inline-flex; - flex-flow: wrap; - row-gap: $spacing-8; - column-gap: $spacing-8; -} diff --git a/res/css/views/settings/_SecureBackupPanel.pcss b/res/css/views/settings/_SecureBackupPanel.pcss deleted file mode 100644 index 6e571af3391..00000000000 --- a/res/css/views/settings/_SecureBackupPanel.pcss +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. -Copyright 2018 New Vector Ltd - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_SecureBackupPanel_deviceName { - font-style: italic; -} - -.mx_SecureBackupPanel_buttonRow { - margin: 1em 0; - display: inline-flex; - flex-flow: wrap; - row-gap: 10px; - - :nth-child(n + 1) { - margin-inline-end: 10px; - } -} - -.mx_SecureBackupPanel_statusList { - border-spacing: 0; - - th { - text-align: start; - } - - td, - th { - padding: 0; - - &:first-of-type { - padding-inline-end: 1em; - } - } -} - -.mx_SecureBackupPanel_advanced { - width: fit-content; -} diff --git a/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.tsx b/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.tsx deleted file mode 100644 index 5e3136c5ad6..00000000000 --- a/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; - -import { _t } from "../../../../languageHandler"; -import BaseDialog from "../BaseDialog"; -import DialogButtons from "../../elements/DialogButtons"; - -interface IProps { - onFinished: (success?: boolean) => void; -} - -export default class ConfirmDestroyCrossSigningDialog extends React.Component { - private onConfirm = (): void => { - this.props.onFinished(true); - }; - - private onDecline = (): void => { - this.props.onFinished(false); - }; - - public render(): React.ReactNode { - return ( - -
-

{_t("encryption|destroy_cross_signing_dialog|warning")}

-
- -
- ); - } -} diff --git a/src/components/views/settings/CrossSigningPanel.tsx b/src/components/views/settings/CrossSigningPanel.tsx deleted file mode 100644 index 9ec9e9f6c18..00000000000 --- a/src/components/views/settings/CrossSigningPanel.tsx +++ /dev/null @@ -1,313 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { ClientEvent, MatrixEvent } from "matrix-js-sdk/src/matrix"; -import { logger } from "matrix-js-sdk/src/logger"; -import { CryptoEvent } from "matrix-js-sdk/src/crypto-api"; - -import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import { _t } from "../../../languageHandler"; -import Modal from "../../../Modal"; -import Spinner from "../elements/Spinner"; -import InteractiveAuthDialog from "../dialogs/InteractiveAuthDialog"; -import ConfirmDestroyCrossSigningDialog from "../dialogs/security/ConfirmDestroyCrossSigningDialog"; -import SetupEncryptionDialog from "../dialogs/security/SetupEncryptionDialog"; -import { accessSecretStorage, withSecretStorageKeyCache } from "../../../SecurityManager"; -import AccessibleButton from "../elements/AccessibleButton"; -import { SettingsSubsectionText } from "./shared/SettingsSubsection"; - -interface IState { - error: boolean; - crossSigningPublicKeysOnDevice?: boolean; - crossSigningPrivateKeysInStorage?: boolean; - masterPrivateKeyCached?: boolean; - selfSigningPrivateKeyCached?: boolean; - userSigningPrivateKeyCached?: boolean; - homeserverSupportsCrossSigning?: boolean; - crossSigningReady?: boolean; -} - -export default class CrossSigningPanel extends React.PureComponent<{}, IState> { - private unmounted = false; - - public constructor(props: {}) { - super(props); - - this.state = { - error: false, - }; - } - - public componentDidMount(): void { - this.unmounted = false; - const cli = MatrixClientPeg.safeGet(); - cli.on(ClientEvent.AccountData, this.onAccountData); - cli.on(CryptoEvent.UserTrustStatusChanged, this.onStatusChanged); - cli.on(CryptoEvent.KeysChanged, this.onStatusChanged); - this.getUpdatedStatus(); - } - - public componentWillUnmount(): void { - this.unmounted = true; - const cli = MatrixClientPeg.get(); - if (!cli) return; - cli.removeListener(ClientEvent.AccountData, this.onAccountData); - cli.removeListener(CryptoEvent.UserTrustStatusChanged, this.onStatusChanged); - cli.removeListener(CryptoEvent.KeysChanged, this.onStatusChanged); - } - - private onAccountData = (event: MatrixEvent): void => { - const type = event.getType(); - if (type.startsWith("m.cross_signing") || type.startsWith("m.secret_storage")) { - this.getUpdatedStatus(); - } - }; - - private onBootstrapClick = (): void => { - if (this.state.crossSigningPrivateKeysInStorage) { - Modal.createDialog(SetupEncryptionDialog, {}, undefined, /* priority = */ false, /* static = */ true); - } else { - // Trigger the flow to set up secure backup, which is what this will do when in - // the appropriate state. - accessSecretStorage(); - } - }; - - private onStatusChanged = (): void => { - this.getUpdatedStatus(); - }; - - private async getUpdatedStatus(): Promise { - const cli = MatrixClientPeg.safeGet(); - const crypto = cli.getCrypto(); - if (!crypto) return; - - const crossSigningStatus = await crypto.getCrossSigningStatus(); - const crossSigningPublicKeysOnDevice = crossSigningStatus.publicKeysOnDevice; - const crossSigningPrivateKeysInStorage = crossSigningStatus.privateKeysInSecretStorage; - const masterPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.masterKey; - const selfSigningPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.selfSigningKey; - const userSigningPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.userSigningKey; - const homeserverSupportsCrossSigning = - await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); - const crossSigningReady = await crypto.isCrossSigningReady(); - - this.setState({ - crossSigningPublicKeysOnDevice, - crossSigningPrivateKeysInStorage, - masterPrivateKeyCached, - selfSigningPrivateKeyCached, - userSigningPrivateKeyCached, - homeserverSupportsCrossSigning, - crossSigningReady, - }); - } - - /** - * Reset the user's cross-signing keys. - */ - private async resetCrossSigning(): Promise { - this.setState({ error: false }); - try { - const cli = MatrixClientPeg.safeGet(); - await withSecretStorageKeyCache(async () => { - await cli.getCrypto()!.bootstrapCrossSigning({ - authUploadDeviceSigningKeys: async (makeRequest): Promise => { - const { finished } = Modal.createDialog(InteractiveAuthDialog, { - title: _t("encryption|bootstrap_title"), - matrixClient: cli, - makeRequest, - }); - const [confirmed] = await finished; - if (!confirmed) { - throw new Error("Cross-signing key upload auth canceled"); - } - }, - setupNewCrossSigning: true, - }); - }); - } catch (e) { - this.setState({ error: true }); - logger.error("Error bootstrapping cross-signing", e); - } - if (this.unmounted) return; - this.getUpdatedStatus(); - } - - /** - * Callback for when the user clicks the "reset cross signing" button. - * - * Shows a confirmation dialog, and then does the reset if confirmed. - */ - private onResetCrossSigningClick = (): void => { - Modal.createDialog(ConfirmDestroyCrossSigningDialog, { - onFinished: async (act) => { - if (!act) return; - this.resetCrossSigning(); - }, - }); - }; - - public render(): React.ReactNode { - const { - error, - crossSigningPublicKeysOnDevice, - crossSigningPrivateKeysInStorage, - masterPrivateKeyCached, - selfSigningPrivateKeyCached, - userSigningPrivateKeyCached, - homeserverSupportsCrossSigning, - crossSigningReady, - } = this.state; - - let errorSection; - if (error) { - errorSection =
{error.toString()}
; - } - - let summarisedStatus; - if (homeserverSupportsCrossSigning === undefined) { - summarisedStatus = ; - } else if (!homeserverSupportsCrossSigning) { - summarisedStatus = ( - - {_t("encryption|cross_signing_unsupported")} - - ); - } else if (crossSigningReady && crossSigningPrivateKeysInStorage) { - summarisedStatus = ( - - ✅ {_t("encryption|cross_signing_ready")} - - ); - } else if (crossSigningReady && !crossSigningPrivateKeysInStorage) { - summarisedStatus = ( - - ⚠️ {_t("encryption|cross_signing_ready_no_backup")} - - ); - } else if (crossSigningPrivateKeysInStorage) { - summarisedStatus = ( - - {_t("encryption|cross_signing_untrusted")} - - ); - } else { - summarisedStatus = ( - - {_t("encryption|cross_signing_not_ready")} - - ); - } - - const keysExistAnywhere = - crossSigningPublicKeysOnDevice || - crossSigningPrivateKeysInStorage || - masterPrivateKeyCached || - selfSigningPrivateKeyCached || - userSigningPrivateKeyCached; - const keysExistEverywhere = - crossSigningPublicKeysOnDevice && - crossSigningPrivateKeysInStorage && - masterPrivateKeyCached && - selfSigningPrivateKeyCached && - userSigningPrivateKeyCached; - - const actions: JSX.Element[] = []; - - // TODO: determine how better to expose this to users in addition to prompts at login/toast - if (!keysExistEverywhere && homeserverSupportsCrossSigning) { - let buttonCaption = _t("encryption|set_up_toast_title"); - if (crossSigningPrivateKeysInStorage) { - buttonCaption = _t("encryption|verify_toast_title"); - } - actions.push( - - {buttonCaption} - , - ); - } - - if (keysExistAnywhere) { - actions.push( - - {_t("action|reset")} - , - ); - } - - let actionRow; - if (actions.length) { - actionRow =
{actions}
; - } - - return ( - <> - {summarisedStatus} -
- {_t("common|advanced")} - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{_t("settings|security|cross_signing_public_keys")} - {crossSigningPublicKeysOnDevice - ? _t("settings|security|cross_signing_in_memory") - : _t("settings|security|cross_signing_not_found")} -
{_t("settings|security|cross_signing_private_keys")} - {crossSigningPrivateKeysInStorage - ? _t("settings|security|cross_signing_in_4s") - : _t("settings|security|cross_signing_not_in_4s")} -
{_t("settings|security|cross_signing_master_private_Key")} - {masterPrivateKeyCached - ? _t("settings|security|cross_signing_cached") - : _t("settings|security|cross_signing_not_cached")} -
{_t("settings|security|cross_signing_self_signing_private_key")} - {selfSigningPrivateKeyCached - ? _t("settings|security|cross_signing_cached") - : _t("settings|security|cross_signing_not_cached")} -
{_t("settings|security|cross_signing_user_signing_private_key")} - {userSigningPrivateKeyCached - ? _t("settings|security|cross_signing_cached") - : _t("settings|security|cross_signing_not_cached")} -
{_t("settings|security|cross_signing_homeserver_support")} - {homeserverSupportsCrossSigning - ? _t("settings|security|cross_signing_homeserver_support_exists") - : _t("settings|security|cross_signing_not_found")} -
-
- {errorSection} - {actionRow} - - ); - } -} diff --git a/src/components/views/settings/CryptographyPanel.tsx b/src/components/views/settings/CryptographyPanel.tsx deleted file mode 100644 index beb08ab1e92..00000000000 --- a/src/components/views/settings/CryptographyPanel.tsx +++ /dev/null @@ -1,147 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2021 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React, { lazy } from "react"; -import { logger } from "matrix-js-sdk/src/logger"; - -import { _t } from "../../../languageHandler"; -import Modal from "../../../Modal"; -import AccessibleButton from "../elements/AccessibleButton"; -import * as FormattingUtils from "../../../utils/FormattingUtils"; -import SettingsStore from "../../../settings/SettingsStore"; -import SettingsFlag from "../elements/SettingsFlag"; -import { SettingLevel } from "../../../settings/SettingLevel"; -import { SettingsSubsection, SettingsSubsectionText } from "./shared/SettingsSubsection"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; - -interface IProps {} - -interface IState { - /** The device's base64-encoded Ed25519 identity key, or: - * - * * `undefined`: not yet loaded - * * `null`: encryption is not supported (or the crypto stack was not correctly initialized) - */ - deviceIdentityKey: string | undefined | null; -} - -export default class CryptographyPanel extends React.Component { - public static contextType = MatrixClientContext; - declare public context: React.ContextType; - - public constructor(props: IProps, context: React.ContextType) { - super(props); - - if (!context.getCrypto()) { - this.state = { deviceIdentityKey: null }; - } else { - this.state = { deviceIdentityKey: undefined }; - } - } - - public componentDidMount(): void { - if (this.state.deviceIdentityKey === undefined) { - this.context - .getCrypto() - ?.getOwnDeviceKeys() - .then((keys) => { - this.setState({ deviceIdentityKey: keys.ed25519 }); - }) - .catch((e) => { - logger.error(`CryptographyPanel: Error fetching own device keys: ${e}`); - this.setState({ deviceIdentityKey: null }); - }); - } - } - - public render(): React.ReactNode { - const client = this.context; - const deviceId = client.deviceId; - let identityKey = this.state.deviceIdentityKey; - if (identityKey === undefined) { - // Should show a spinner here really, but since this will be very transitional, I can't be doing with the - // necessary styling. - identityKey = "..."; - } else if (identityKey === null) { - identityKey = _t("encryption|not_supported"); - } else { - identityKey = FormattingUtils.formatCryptoKey(identityKey); - } - - let importExportButtons: JSX.Element | undefined; - if (client.getCrypto()) { - importExportButtons = ( -
- - {_t("settings|security|export_megolm_keys")} - - - {_t("settings|security|import_megolm_keys")} - -
- ); - } - - let noSendUnverifiedSetting: JSX.Element | undefined; - if (SettingsStore.canSetValue("blacklistUnverifiedDevices", null, SettingLevel.DEVICE)) { - noSendUnverifiedSetting = ( - - ); - } - - return ( - - - - - - - - - - - - - -
{_t("settings|security|session_id")} - {deviceId} -
{_t("settings|security|session_key")} - - {identityKey} - -
-
- {importExportButtons} - {noSendUnverifiedSetting} -
- ); - } - - private onExportE2eKeysClicked = (): void => { - Modal.createDialog( - lazy(() => import("../../../async-components/views/dialogs/security/ExportE2eKeysDialog")), - { matrixClient: this.context }, - ); - }; - - private onImportE2eKeysClicked = (): void => { - Modal.createDialog( - lazy(() => import("../../../async-components/views/dialogs/security/ImportE2eKeysDialog")), - { matrixClient: this.context }, - ); - }; - - private updateBlacklistDevicesFlag = (checked: boolean): void => { - const crypto = this.context.getCrypto(); - if (crypto) crypto.globalBlacklistUnverifiedDevices = checked; - }; -} diff --git a/src/components/views/settings/SecureBackupPanel.tsx b/src/components/views/settings/SecureBackupPanel.tsx deleted file mode 100644 index 3d245678320..00000000000 --- a/src/components/views/settings/SecureBackupPanel.tsx +++ /dev/null @@ -1,420 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. -Copyright 2018 New Vector Ltd - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React, { lazy, ReactNode } from "react"; -import { CryptoEvent, BackupTrustInfo, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api"; -import { logger } from "matrix-js-sdk/src/logger"; - -import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import { _t } from "../../../languageHandler"; -import Modal from "../../../Modal"; -import { isSecureBackupRequired } from "../../../utils/WellKnownUtils"; -import Spinner from "../elements/Spinner"; -import AccessibleButton from "../elements/AccessibleButton"; -import QuestionDialog from "../dialogs/QuestionDialog"; -import RestoreKeyBackupDialog from "../dialogs/security/RestoreKeyBackupDialog"; -import { accessSecretStorage } from "../../../SecurityManager"; -import { SettingsSubsectionText } from "./shared/SettingsSubsection"; - -interface IState { - loading: boolean; - error: boolean; - backupKeyStored: boolean | null; - backupKeyCached: boolean | null; - backupKeyWellFormed: boolean | null; - secretStorageKeyInAccount: boolean | null; - secretStorageReady: boolean | null; - - /** Information on the current key backup version, as returned by the server. - * - * `null` could mean any of: - * * we haven't yet requested the data from the server. - * * we were unable to reach the server. - * * the server returned key backup version data we didn't understand or was malformed. - * * there is actually no backup on the server. - */ - backupInfo: KeyBackupInfo | null; - - /** - * Information on whether the backup in `backupInfo` is correctly signed, and whether we have the right key to - * decrypt it. - * - * `undefined` if `backupInfo` is null, or if crypto is not enabled in the client. - */ - backupTrustInfo: BackupTrustInfo | undefined; - - /** - * If key backup is currently enabled, the backup version we are backing up to. - */ - activeBackupVersion: string | null; - - /** - * Number of sessions remaining to be backed up. `null` if we have no information on this. - */ - sessionsRemaining: number | null; -} - -export default class SecureBackupPanel extends React.PureComponent<{}, IState> { - private unmounted = false; - - public constructor(props: {}) { - super(props); - - this.state = { - loading: true, - error: false, - backupKeyStored: null, - backupKeyCached: null, - backupKeyWellFormed: null, - secretStorageKeyInAccount: null, - secretStorageReady: null, - backupInfo: null, - backupTrustInfo: undefined, - activeBackupVersion: null, - sessionsRemaining: null, - }; - } - - public componentDidMount(): void { - this.unmounted = false; - this.loadBackupStatus(); - - MatrixClientPeg.safeGet().on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); - MatrixClientPeg.safeGet().on(CryptoEvent.KeyBackupSessionsRemaining, this.onKeyBackupSessionsRemaining); - } - - public componentWillUnmount(): void { - this.unmounted = true; - - if (MatrixClientPeg.get()) { - MatrixClientPeg.get()!.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); - MatrixClientPeg.get()!.removeListener( - CryptoEvent.KeyBackupSessionsRemaining, - this.onKeyBackupSessionsRemaining, - ); - } - } - - private onKeyBackupSessionsRemaining = (sessionsRemaining: number): void => { - this.setState({ - sessionsRemaining, - }); - }; - - private onKeyBackupStatus = (): void => { - // This just loads the current backup status rather than forcing - // a re-check otherwise we risk causing infinite loops - this.loadBackupStatus(); - }; - - private async loadBackupStatus(): Promise { - this.setState({ loading: true }); - this.getUpdatedDiagnostics(); - try { - const cli = MatrixClientPeg.safeGet(); - const backupInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null; - const backupTrustInfo = backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined; - - const activeBackupVersion = (await cli.getCrypto()?.getActiveSessionBackupVersion()) ?? null; - - if (this.unmounted) return; - this.setState({ - loading: false, - error: false, - backupInfo, - backupTrustInfo, - activeBackupVersion, - }); - } catch (e) { - logger.log("Unable to fetch key backup status", e); - if (this.unmounted) return; - this.setState({ - loading: false, - error: true, - backupInfo: null, - backupTrustInfo: undefined, - activeBackupVersion: null, - }); - } - } - - private async getUpdatedDiagnostics(): Promise { - const cli = MatrixClientPeg.safeGet(); - const crypto = cli.getCrypto(); - if (!crypto) return; - - const secretStorage = cli.secretStorage; - - const backupKeyStored = !!(await cli.isKeyBackupKeyStored()); - const backupKeyFromCache = await crypto.getSessionBackupPrivateKey(); - const backupKeyCached = !!backupKeyFromCache; - const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array; - const secretStorageKeyInAccount = await secretStorage.hasKey(); - const secretStorageReady = await crypto.isSecretStorageReady(); - - if (this.unmounted) return; - this.setState({ - backupKeyStored, - backupKeyCached, - backupKeyWellFormed, - secretStorageKeyInAccount, - secretStorageReady, - }); - } - - private startNewBackup = (): void => { - Modal.createDialog( - lazy(() => import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog")), - { - onFinished: () => { - this.loadBackupStatus(); - }, - }, - undefined, - /* priority = */ false, - /* static = */ true, - ); - }; - - private deleteBackup = (): void => { - Modal.createDialog(QuestionDialog, { - title: _t("settings|security|delete_backup"), - description: _t("settings|security|delete_backup_confirm_description"), - button: _t("settings|security|delete_backup"), - danger: true, - onFinished: (proceed) => { - if (!proceed) return; - this.setState({ loading: true }); - const versionToDelete = this.state.backupInfo!.version!; - // deleteKeyBackupVersion fires a key backup status event - // which will update the UI - MatrixClientPeg.safeGet().getCrypto()?.deleteKeyBackupVersion(versionToDelete); - }, - }); - }; - - private restoreBackup = async (): Promise => { - Modal.createDialog(RestoreKeyBackupDialog, undefined, undefined, /* priority = */ false, /* static = */ true); - }; - - private resetSecretStorage = async (): Promise => { - this.setState({ error: false }); - try { - await accessSecretStorage(async (): Promise => {}, { forceReset: true }); - } catch (e) { - logger.error("Error resetting secret storage", e); - if (this.unmounted) return; - this.setState({ error: true }); - } - if (this.unmounted) return; - this.loadBackupStatus(); - }; - - public render(): React.ReactNode { - const { - loading, - error, - backupKeyStored, - backupKeyCached, - backupKeyWellFormed, - secretStorageKeyInAccount, - secretStorageReady, - backupInfo, - backupTrustInfo, - sessionsRemaining, - } = this.state; - - let statusDescription: JSX.Element; - let extraDetailsTableRows: JSX.Element | undefined; - let extraDetails: JSX.Element | undefined; - const actions: JSX.Element[] = []; - if (error) { - statusDescription = ( - - {_t("settings|security|error_loading_key_backup_status")} - - ); - } else if (loading) { - statusDescription = ; - } else if (backupInfo) { - let restoreButtonCaption = _t("settings|security|restore_key_backup"); - - if (this.state.activeBackupVersion !== null) { - statusDescription = ( - ✅ {_t("settings|security|key_backup_active")} - ); - } else { - statusDescription = ( - <> - - {_t("settings|security|key_backup_inactive", {}, { b: (sub) => {sub} })} - - - {_t("settings|security|key_backup_connect_prompt")} - - - ); - restoreButtonCaption = _t("settings|security|key_backup_connect"); - } - - let uploadStatus: ReactNode; - if (sessionsRemaining === null) { - // No upload status to show when backup disabled. - uploadStatus = ""; - } else if (sessionsRemaining > 0) { - uploadStatus = ( -
- {_t("settings|security|key_backup_in_progress", { sessionsRemaining })}
-
- ); - } else { - uploadStatus = ( -
- {_t("settings|security|key_backup_complete")}
-
- ); - } - - let trustedLocally: string | undefined; - if (backupTrustInfo?.matchesDecryptionKey) { - trustedLocally = _t("settings|security|key_backup_can_be_restored"); - } - - extraDetailsTableRows = ( - <> - - {_t("settings|security|key_backup_latest_version")} - - {backupInfo.version} ({_t("settings|security|key_backup_algorithm")}{" "} - {backupInfo.algorithm}) - - - - {_t("settings|security|key_backup_active_version")} - - {this.state.activeBackupVersion === null - ? _t("settings|security|key_backup_active_version_none") - : this.state.activeBackupVersion} - - - - ); - - extraDetails = ( - <> - {uploadStatus} -
{trustedLocally}
- - ); - - actions.push( - - {restoreButtonCaption} - , - ); - - if (!isSecureBackupRequired(MatrixClientPeg.safeGet())) { - actions.push( - - {_t("settings|security|delete_backup")} - , - ); - } - } else { - statusDescription = ( - <> - - {_t( - "settings|security|key_backup_inactive_warning", - {}, - { b: (sub) => {sub} }, - )} - - {_t("encryption|setup_secure_backup|explainer")} - - ); - actions.push( - - {_t("encryption|setup_secure_backup|title")} - , - ); - } - - if (secretStorageKeyInAccount) { - actions.push( - - {_t("action|reset")} - , - ); - } - - let backupKeyWellFormedText = ""; - if (backupKeyCached) { - backupKeyWellFormedText = ", "; - if (backupKeyWellFormed) { - backupKeyWellFormedText += _t("settings|security|backup_key_well_formed"); - } else { - backupKeyWellFormedText += _t("settings|security|backup_key_unexpected_type"); - } - } - - let actionRow: JSX.Element | undefined; - if (actions.length) { - actionRow =
{actions}
; - } - - return ( - <> - {_t("settings|security|backup_keys_description")} - {statusDescription} -
- {_t("common|advanced")} - - - - - - - - - - - - - - - - - - {extraDetailsTableRows} -
{_t("settings|security|backup_key_stored_status")} - {backupKeyStored === true - ? _t("settings|security|cross_signing_in_4s") - : _t("settings|security|cross_signing_not_stored")} -
{_t("settings|security|backup_key_cached_status")} - {backupKeyCached - ? _t("settings|security|cross_signing_cached") - : _t("settings|security|cross_signing_not_cached")} - {backupKeyWellFormedText} -
{_t("settings|security|4s_public_key_status")} - {secretStorageKeyInAccount - ? _t("settings|security|4s_public_key_in_account_data") - : _t("settings|security|cross_signing_not_found")} -
{_t("settings|security|secret_storage_status")} - {secretStorageReady - ? _t("settings|security|secret_storage_ready") - : _t("settings|security|secret_storage_not_ready")} -
- {extraDetails} -
- {actionRow} - - ); - } -} diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx index dfd57aa0725..7e78bb61419 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx @@ -17,13 +17,10 @@ import { MatrixClientPeg } from "../../../../../MatrixClientPeg"; import AccessibleButton from "../../../elements/AccessibleButton"; import dis from "../../../../../dispatcher/dispatcher"; import { SettingLevel } from "../../../../../settings/SettingLevel"; -import SecureBackupPanel from "../../SecureBackupPanel"; import SettingsStore from "../../../../../settings/SettingsStore"; import { UIFeature } from "../../../../../settings/UIFeature"; import { ActionPayload } from "../../../../../dispatcher/payloads"; -import CryptographyPanel from "../../CryptographyPanel"; import SettingsFlag from "../../../elements/SettingsFlag"; -import CrossSigningPanel from "../../CrossSigningPanel"; import EventIndexPanel from "../../EventIndexPanel"; import InlineSpinner from "../../../elements/InlineSpinner"; import { PosthogAnalytics } from "../../../../../PosthogAnalytics"; @@ -33,7 +30,6 @@ import type { IServerVersions } from "matrix-js-sdk/src/matrix"; import SettingsTab from "../SettingsTab"; import { SettingsSection } from "../../shared/SettingsSection"; import { SettingsSubsection, SettingsSubsectionText } from "../../shared/SettingsSubsection"; -import { useOwnDevices } from "../../devices/useOwnDevices"; import { DiscoverySettings } from "../../discovery/DiscoverySettings"; import SetIntegrationManager from "../../SetIntegrationManager"; @@ -43,23 +39,6 @@ interface IIgnoredUserProps { inProgress: boolean; } -const DehydratedDeviceStatus: React.FC = () => { - const { dehydratedDeviceId } = useOwnDevices(); - - if (dehydratedDeviceId) { - return ( -
-
{_t("settings|security|dehydrated_device_enabled")}
-
- {_t("settings|security|dehydrated_device_description")} -
-
- ); - } else { - return null; - } -}; - export class IgnoredUser extends React.Component { private onUnignoreClicked = (): void => { this.props.onUnignored(this.props.userId); @@ -287,29 +266,12 @@ export default class SecurityUserSettingsTab extends React.Component - - - - ); - const eventIndex = ( ); - // XXX: There's no such panel in the current cross-signing designs, but - // it's useful to have for testing the feature. If there's no interest - // in having advanced details here once all flows are implemented, we - // can remove this. - const crossSigning = ( - - - - ); - let warning; if (!privateShouldBeEncrypted(MatrixClientPeg.safeGet())) { warning = ( @@ -366,12 +328,7 @@ export default class SecurityUserSettingsTab extends React.Component {warning} - - {secureBackup} - {eventIndex} - {crossSigning} - - + {eventIndex} {posthogSection} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a9825a16e4d..09a11d87a70 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -464,7 +464,6 @@ "capabilities": "Capabilities", "copied": "Copied!", "credits": "Credits", - "cross_signing": "Cross-signing", "dark": "Dark", "description": "Description", "deselect_all": "Deselect all", @@ -547,7 +546,6 @@ "save": "Save", "saved": "Saved", "saving": "Saving…", - "secure_backup": "Secure Backup", "security": "Security", "select_all": "Select all", "server": "Server", @@ -901,22 +899,12 @@ "cancel_entering_passphrase_title": "Cancel entering passphrase?", "confirm_encryption_setup_body": "Click the button below to confirm setting up encryption.", "confirm_encryption_setup_title": "Confirm encryption setup", - "cross_signing_not_ready": "Cross-signing is not set up.", - "cross_signing_ready": "Cross-signing is ready for use.", - "cross_signing_ready_no_backup": "Cross-signing is ready but keys are not backed up.", "cross_signing_room_normal": "This room is end-to-end encrypted", "cross_signing_room_verified": "Everyone in this room is verified", "cross_signing_room_warning": "Someone is using an unknown session", - "cross_signing_unsupported": "Your homeserver does not support cross-signing.", - "cross_signing_untrusted": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", "cross_signing_user_normal": "You have not verified this user.", "cross_signing_user_verified": "You have verified this user. This user has verified all of their sessions.", "cross_signing_user_warning": "This user has not verified all of their sessions.", - "destroy_cross_signing_dialog": { - "primary_button_text": "Clear cross-signing keys", - "title": "Destroy cross-signing keys?", - "warning": "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from." - }, "enter_recovery_key": "Enter recovery key", "event_shield_reason_authenticity_not_guaranteed": "The authenticity of this encrypted message can't be guaranteed on this device.", "event_shield_reason_mismatched_sender_key": "Encrypted by an unverified session", @@ -943,7 +931,6 @@ "title": "New Recovery Method", "warning": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings." }, - "not_supported": "", "pinned_identity_changed": "%(displayName)s's (%(userId)s) identity appears to have changed. Learn more", "pinned_identity_changed_no_displayname": "%(userId)s's identity appears to have changed. Learn more", "recovery_method_removed": { @@ -959,8 +946,7 @@ "set_up_toast_description": "Safeguard against losing access to encrypted messages & data", "set_up_toast_title": "Set up Secure Backup", "setup_secure_backup": { - "explainer": "Back up your keys before signing out to avoid losing them.", - "title": "Set up" + "explainer": "Back up your keys before signing out to avoid losing them." }, "udd": { "interactive_verification_button": "Interactively verify by emoji", @@ -2740,57 +2726,18 @@ "prompt_invite": "Prompt before sending invites to potentially invalid matrix IDs", "replace_plain_emoji": "Automatically replace plain text Emoji", "security": { - "4s_public_key_in_account_data": "in account data", - "4s_public_key_status": "Secret storage public key:", "analytics_description": "Share anonymous data to help us identify issues. Nothing personal. No third parties.", - "backup_key_cached_status": "Backup key cached:", - "backup_key_stored_status": "Backup key stored:", - "backup_key_unexpected_type": "unexpected type", - "backup_key_well_formed": "well formed", - "backup_keys_description": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.", "bulk_options_accept_all_invites": "Accept all %(invitedRooms)s invites", "bulk_options_reject_all_invites": "Reject all %(invitedRooms)s invites", "bulk_options_section": "Bulk options", - "cross_signing_cached": "cached locally", - "cross_signing_homeserver_support": "Homeserver feature support:", - "cross_signing_homeserver_support_exists": "exists", - "cross_signing_in_4s": "in secret storage", - "cross_signing_in_memory": "in memory", - "cross_signing_master_private_Key": "Master private key:", - "cross_signing_not_cached": "not found locally", - "cross_signing_not_found": "not found", - "cross_signing_not_in_4s": "not found in storage", - "cross_signing_not_stored": "not stored", - "cross_signing_private_keys": "Cross-signing private keys:", - "cross_signing_public_keys": "Cross-signing public keys:", - "cross_signing_self_signing_private_key": "Self signing private key:", - "cross_signing_user_signing_private_key": "User signing private key:", - "cryptography_section": "Cryptography", - "dehydrated_device_description": "The offline device feature allows you to receive encrypted messages even when you are not logged in to any devices", - "dehydrated_device_enabled": "Offline device enabled", - "delete_backup": "Delete Backup", - "delete_backup_confirm_description": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", "dialog_title": "Settings: Security & Privacy", "e2ee_default_disabled_warning": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.", "enable_message_search": "Enable message search in encrypted rooms", "encryption_section": "Encryption", - "error_loading_key_backup_status": "Unable to load key backup status", - "export_megolm_keys": "Export E2E room keys", "ignore_users_empty": "You have no ignored users.", "ignore_users_section": "Ignored users", - "import_megolm_keys": "Import E2E room keys", - "key_backup_active": "This session is backing up your keys.", - "key_backup_active_version": "Active backup version:", - "key_backup_active_version_none": "None", "key_backup_algorithm": "Algorithm:", - "key_backup_can_be_restored": "This backup can be restored on this session", - "key_backup_complete": "All keys backed up", "key_backup_connect": "Connect this session to Key Backup", - "key_backup_connect_prompt": "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.", - "key_backup_in_progress": "Backing up %(sessionsRemaining)s keys…", - "key_backup_inactive": "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", - "key_backup_inactive_warning": "Your keys are not being backed up from this session.", - "key_backup_latest_version": "Latest backup version on server:", "message_search_disable_warning": "If disabled, messages from encrypted rooms won't appear in search results.", "message_search_disabled": "Securely cache encrypted messages locally for them to appear in search results.", "message_search_enabled": { @@ -2810,13 +2757,7 @@ "message_search_unsupported": "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.", "message_search_unsupported_web": "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.", "record_session_details": "Record the client name, version, and url to recognise sessions more easily in session manager", - "restore_key_backup": "Restore from Backup", - "secret_storage_not_ready": "not ready", - "secret_storage_ready": "ready", - "secret_storage_status": "Secret storage:", "send_analytics": "Send analytics data", - "session_id": "Session ID:", - "session_key": "Session key:", "strict_encryption": "Never send encrypted messages to unverified sessions from this session" }, "send_read_receipts": "Send read receipts", diff --git a/src/utils/FormattingUtils.ts b/src/utils/FormattingUtils.ts index ae92cf61049..332288da533 100644 --- a/src/utils/FormattingUtils.ts +++ b/src/utils/FormattingUtils.ts @@ -56,17 +56,6 @@ export function formatBytes(bytes: number, decimals = 2): string { return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; } -/** - * format a key into groups of 4 characters, for easier visual inspection - * - * @param {string} key key to format - * - * @return {string} - */ -export function formatCryptoKey(key: string): string { - return key.match(/.{1,4}/g)!.join(" "); -} - export function getUserNameColorClass(userId: string): string { // eslint-disable-next-line react-hooks/rules-of-hooks const number = useIdColorHash(userId); diff --git a/test/unit-tests/components/views/settings/CrossSigningPanel-test.tsx b/test/unit-tests/components/views/settings/CrossSigningPanel-test.tsx deleted file mode 100644 index ee7c6425878..00000000000 --- a/test/unit-tests/components/views/settings/CrossSigningPanel-test.tsx +++ /dev/null @@ -1,133 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { render, screen } from "jest-matrix-react"; -import { Mocked, mocked } from "jest-mock"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; - -import CrossSigningPanel from "../../../../../src/components/views/settings/CrossSigningPanel"; -import { - flushPromises, - getMockClientWithEventEmitter, - mockClientMethodsCrypto, - mockClientMethodsUser, -} from "../../../../test-utils"; -import Modal from "../../../../../src/Modal"; -import ConfirmDestroyCrossSigningDialog from "../../../../../src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog"; - -describe("", () => { - const userId = "@alice:server.org"; - let mockClient: Mocked; - const getComponent = () => render(); - - beforeEach(() => { - mockClient = getMockClientWithEventEmitter({ - ...mockClientMethodsUser(userId), - ...mockClientMethodsCrypto(), - doesServerSupportUnstableFeature: jest.fn(), - }); - - mockClient.doesServerSupportUnstableFeature.mockResolvedValue(true); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it("should render a spinner while loading", () => { - getComponent(); - - expect(screen.getByRole("progressbar")).toBeInTheDocument(); - }); - - it("should render when homeserver does not support cross-signing", async () => { - mockClient.doesServerSupportUnstableFeature.mockResolvedValue(false); - - getComponent(); - await flushPromises(); - - expect(screen.getByText("Your homeserver does not support cross-signing.")).toBeInTheDocument(); - }); - - describe("when cross signing is ready", () => { - it("should render when keys are not backed up", async () => { - getComponent(); - await flushPromises(); - - expect(screen.getByTestId("summarised-status").innerHTML).toEqual( - "⚠️ Cross-signing is ready but keys are not backed up.", - ); - expect(screen.getByText("Cross-signing private keys:").parentElement!).toMatchSnapshot(); - }); - - it("should render when keys are backed up", async () => { - mocked(mockClient.getCrypto()!.getCrossSigningStatus).mockResolvedValue({ - publicKeysOnDevice: true, - privateKeysInSecretStorage: true, - privateKeysCachedLocally: { - masterKey: true, - selfSigningKey: true, - userSigningKey: true, - }, - }); - getComponent(); - await flushPromises(); - - expect(screen.getByTestId("summarised-status").innerHTML).toEqual("✅ Cross-signing is ready for use."); - expect(screen.getByText("Cross-signing private keys:").parentElement!).toMatchSnapshot(); - }); - - it("should allow reset of cross-signing", async () => { - mockClient.getCrypto()!.bootstrapCrossSigning = jest.fn().mockResolvedValue(undefined); - getComponent(); - await flushPromises(); - - const modalSpy = jest.spyOn(Modal, "createDialog"); - - screen.getByRole("button", { name: "Reset" }).click(); - expect(modalSpy).toHaveBeenCalledWith(ConfirmDestroyCrossSigningDialog, expect.any(Object)); - modalSpy.mock.lastCall![1]!.onFinished(true); - expect(mockClient.getCrypto()!.bootstrapCrossSigning).toHaveBeenCalledWith( - expect.objectContaining({ setupNewCrossSigning: true }), - ); - }); - }); - - describe("when cross signing is not ready", () => { - beforeEach(() => { - mocked(mockClient.getCrypto()!.isCrossSigningReady).mockResolvedValue(false); - }); - - it("should render when keys are not backed up", async () => { - getComponent(); - await flushPromises(); - - expect(screen.getByTestId("summarised-status").innerHTML).toEqual("Cross-signing is not set up."); - }); - - it("should render when keys are backed up", async () => { - mocked(mockClient.getCrypto()!.getCrossSigningStatus).mockResolvedValue({ - publicKeysOnDevice: true, - privateKeysInSecretStorage: true, - privateKeysCachedLocally: { - masterKey: true, - selfSigningKey: true, - userSigningKey: true, - }, - }); - getComponent(); - await flushPromises(); - - expect(screen.getByTestId("summarised-status").innerHTML).toEqual( - "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", - ); - expect(screen.getByText("Cross-signing private keys:").parentElement!).toMatchSnapshot(); - }); - }); -}); diff --git a/test/unit-tests/components/views/settings/CryptographyPanel-test.tsx b/test/unit-tests/components/views/settings/CryptographyPanel-test.tsx deleted file mode 100644 index 3d368734731..00000000000 --- a/test/unit-tests/components/views/settings/CryptographyPanel-test.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { render, waitFor, screen, fireEvent } from "jest-matrix-react"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; -import { mocked } from "jest-mock"; - -import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg"; -import * as TestUtils from "../../../../test-utils"; -import CryptographyPanel from "../../../../../src/components/views/settings/CryptographyPanel"; -import { withClientContextRenderOptions } from "../../../../test-utils"; - -describe("CryptographyPanel", () => { - it("shows the session ID and key", async () => { - const sessionId = "ABCDEFGHIJ"; - const sessionKey = "AbCDeFghIJK7L/m4nOPqRSTUVW4xyzaBCDef6gHIJkl"; - const sessionKeyFormatted = "AbCD eFgh IJK7 L/m4 nOPq RSTU VW4x yzaB CDef 6gHI Jkl"; - - TestUtils.stubClient(); - const client: MatrixClient = MatrixClientPeg.safeGet(); - client.deviceId = sessionId; - - mocked(client.getCrypto()!.getOwnDeviceKeys).mockResolvedValue({ ed25519: sessionKey, curve25519: "1234" }); - - // When we render the CryptographyPanel - const rendered = render(, withClientContextRenderOptions(client)); - - // Then it displays info about the user's session - const codes = rendered.container.querySelectorAll("code"); - expect(codes.length).toEqual(2); - expect(codes[0].innerHTML).toEqual(sessionId); - - // Initially a placeholder - expect(codes[1].innerHTML).toEqual("..."); - - // Then the actual key - await waitFor(() => expect(codes[1].innerHTML).toEqual(sessionKeyFormatted)); - }); - - it("handles errors fetching session key", async () => { - const sessionId = "ABCDEFGHIJ"; - - TestUtils.stubClient(); - const client: MatrixClient = MatrixClientPeg.safeGet(); - client.deviceId = sessionId; - - mocked(client.getCrypto()!.getOwnDeviceKeys).mockRejectedValue(new Error("bleh")); - - // When we render the CryptographyPanel - const rendered = render(, withClientContextRenderOptions(client)); - - // Then it displays info about the user's session - const codes = rendered.container.querySelectorAll("code"); - - // Initially a placeholder - expect(codes[1].innerHTML).toEqual("..."); - - // Then "not supported key - await waitFor(() => expect(codes[1].innerHTML).toEqual("<not supported>")); - }); - - it("should open the export e2e keys dialog on click", async () => { - const sessionId = "ABCDEFGHIJ"; - const sessionKey = "AbCDeFghIJK7L/m4nOPqRSTUVW4xyzaBCDef6gHIJkl"; - - TestUtils.stubClient(); - const client: MatrixClient = MatrixClientPeg.safeGet(); - client.deviceId = sessionId; - - mocked(client.getCrypto()!.getOwnDeviceKeys).mockResolvedValue({ ed25519: sessionKey, curve25519: "1234" }); - - render(, withClientContextRenderOptions(client)); - fireEvent.click(await screen.findByRole("button", { name: "Export E2E room keys" })); - await expect(screen.findByRole("heading", { name: "Export room keys" })).resolves.toBeInTheDocument(); - }); - - it("should open the import e2e keys dialog on click", async () => { - const sessionId = "ABCDEFGHIJ"; - const sessionKey = "AbCDeFghIJK7L/m4nOPqRSTUVW4xyzaBCDef6gHIJkl"; - - TestUtils.stubClient(); - const client: MatrixClient = MatrixClientPeg.safeGet(); - client.deviceId = sessionId; - - mocked(client.getCrypto()!.getOwnDeviceKeys).mockResolvedValue({ ed25519: sessionKey, curve25519: "1234" }); - - render(, withClientContextRenderOptions(client)); - fireEvent.click(await screen.findByRole("button", { name: "Import E2E room keys" })); - await expect(screen.findByRole("heading", { name: "Import room keys" })).resolves.toBeInTheDocument(); - }); -}); diff --git a/test/unit-tests/components/views/settings/SecureBackupPanel-test.tsx b/test/unit-tests/components/views/settings/SecureBackupPanel-test.tsx deleted file mode 100644 index 78d59c07f17..00000000000 --- a/test/unit-tests/components/views/settings/SecureBackupPanel-test.tsx +++ /dev/null @@ -1,171 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { fireEvent, render, screen, within } from "jest-matrix-react"; -import { mocked } from "jest-mock"; - -import { - flushPromises, - getMockClientWithEventEmitter, - mockClientMethodsCrypto, - mockClientMethodsUser, -} from "../../../../test-utils"; -import SecureBackupPanel from "../../../../../src/components/views/settings/SecureBackupPanel"; -import { accessSecretStorage } from "../../../../../src/SecurityManager"; - -jest.mock("../../../../../src/SecurityManager", () => ({ - accessSecretStorage: jest.fn(), -})); - -describe("", () => { - const userId = "@alice:server.org"; - const client = getMockClientWithEventEmitter({ - ...mockClientMethodsUser(userId), - ...mockClientMethodsCrypto(), - getClientWellKnown: jest.fn(), - }); - - const getComponent = () => render(); - - beforeEach(() => { - jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({ - version: "1", - algorithm: "test", - auth_data: { - public_key: "1234", - }, - }); - Object.assign(client.getCrypto()!, { - isKeyBackupTrusted: jest.fn().mockResolvedValue({ - trusted: false, - matchesDecryptionKey: false, - }), - getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null), - deleteKeyBackupVersion: jest.fn().mockResolvedValue(undefined), - }); - - mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(false); - - mocked(accessSecretStorage).mockClear().mockResolvedValue(); - }); - - it("displays a loader while checking keybackup", async () => { - getComponent(); - expect(screen.getByRole("progressbar")).toBeInTheDocument(); - await flushPromises(); - expect(screen.queryByRole("progressbar")).not.toBeInTheDocument(); - }); - - it("handles error fetching backup", async () => { - // getKeyBackupInfo can fail for various reasons - jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockImplementation(async () => { - throw new Error("beep beep"); - }); - const renderResult = getComponent(); - await renderResult.findByText("Unable to load key backup status"); - expect(renderResult.container).toMatchSnapshot(); - }); - - it("handles absence of backup", async () => { - jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockResolvedValue(null); - getComponent(); - // flush getKeyBackupInfo promise - await flushPromises(); - expect(screen.getByText("Back up your keys before signing out to avoid losing them.")).toBeInTheDocument(); - }); - - it("suggests connecting session to key backup when backup exists", async () => { - const { container } = getComponent(); - // flush checkKeyBackup promise - await flushPromises(); - - expect(container).toMatchSnapshot(); - }); - - it("displays when session is connected to key backup", async () => { - mocked(client.getCrypto()!).getActiveSessionBackupVersion.mockResolvedValue("1"); - getComponent(); - // flush checkKeyBackup promise - await flushPromises(); - - expect(screen.getByText("✅ This session is backing up your keys.")).toBeInTheDocument(); - }); - - it("asks for confirmation before deleting a backup", async () => { - getComponent(); - // flush checkKeyBackup promise - await flushPromises(); - - fireEvent.click(screen.getByText("Delete Backup")); - - const dialog = await screen.findByRole("dialog"); - - expect( - within(dialog).getByText( - "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", - ), - ).toBeInTheDocument(); - - fireEvent.click(within(dialog).getByText("Cancel")); - - expect(client.getCrypto()!.deleteKeyBackupVersion).not.toHaveBeenCalled(); - }); - - it("deletes backup after confirmation", async () => { - jest.spyOn(client.getCrypto()!, "getKeyBackupInfo") - .mockResolvedValueOnce({ - version: "1", - algorithm: "test", - auth_data: { - public_key: "1234", - }, - }) - .mockResolvedValue(null); - getComponent(); - - fireEvent.click(await screen.findByText("Delete Backup")); - - const dialog = await screen.findByRole("dialog"); - - expect( - within(dialog).getByText( - "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", - ), - ).toBeInTheDocument(); - - fireEvent.click(within(dialog).getByTestId("dialog-primary-button")); - - expect(client.getCrypto()!.deleteKeyBackupVersion).toHaveBeenCalledWith("1"); - - // delete request - await flushPromises(); - // refresh backup info - await flushPromises(); - }); - - it("resets secret storage", async () => { - mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(true); - getComponent(); - // flush checkKeyBackup promise - await flushPromises(); - - jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockClear(); - mocked(client.getCrypto()!).isKeyBackupTrusted.mockClear(); - - fireEvent.click(screen.getByText("Reset")); - - // enter loading state - expect(accessSecretStorage).toHaveBeenCalled(); - await flushPromises(); - - // backup status refreshed - expect(client.getCrypto()!.getKeyBackupInfo).toHaveBeenCalled(); - expect(client.getCrypto()!.isKeyBackupTrusted).toHaveBeenCalled(); - }); -}); diff --git a/test/unit-tests/components/views/settings/__snapshots__/CrossSigningPanel-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/CrossSigningPanel-test.tsx.snap deleted file mode 100644 index d484ba4a3be..00000000000 --- a/test/unit-tests/components/views/settings/__snapshots__/CrossSigningPanel-test.tsx.snap +++ /dev/null @@ -1,40 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` when cross signing is not ready should render when keys are backed up 1`] = ` - - - Cross-signing private keys: - - - in secret storage - - -`; - -exports[` when cross signing is ready should render when keys are backed up 1`] = ` - - - Cross-signing private keys: - - - in secret storage - - -`; - -exports[` when cross signing is ready should render when keys are not backed up 1`] = ` - - - Cross-signing private keys: - - - not found in storage - - -`; diff --git a/test/unit-tests/components/views/settings/__snapshots__/SecureBackupPanel-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/SecureBackupPanel-test.tsx.snap deleted file mode 100644 index 1e920c24841..00000000000 --- a/test/unit-tests/components/views/settings/__snapshots__/SecureBackupPanel-test.tsx.snap +++ /dev/null @@ -1,190 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` handles error fetching backup 1`] = ` -
-
- Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key. -
-
- Unable to load key backup status -
-
- - Advanced - - - - - - - - - - - - - - - - - - -
- Backup key stored: - - not stored -
- Backup key cached: - - not found locally -
- Secret storage public key: - - not found -
- Secret storage: - - not ready -
-
-
-`; - -exports[` suggests connecting session to key backup when backup exists 1`] = ` -
-
- Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key. -
-
- - This session is - - not backing up your keys - - , but you do have an existing backup you can restore from and add to going forward. - -
-
- Connect this session to key backup before signing out to avoid losing any keys that may only be on this session. -
-
- - Advanced - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Backup key stored: - - not stored -
- Backup key cached: - - not found locally -
- Secret storage public key: - - not found -
- Secret storage: - - not ready -
- Latest backup version on server: - - 1 - ( - Algorithm: - - - test - - ) -
- Active backup version: - - None -
-
-
-
-
- Connect this session to Key Backup -
-
- Delete Backup -
-
-
-`; diff --git a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SecurityUserSettingsTab-test.tsx.snap b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SecurityUserSettingsTab-test.tsx.snap index fa77b76b68d..26dc4ebeca6 100644 --- a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SecurityUserSettingsTab-test.tsx.snap +++ b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SecurityUserSettingsTab-test.tsx.snap @@ -71,90 +71,6 @@ exports[` renders security section 1`] = `
-
-
-

- Secure Backup -

-
-
-
- Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key. -
-
-
-
-
- - Advanced - - - - - - - - - - - - - - - - - - -
- Backup key stored: - - not stored -
- Backup key cached: - - not found locally -
- Secret storage public key: - - not found -
- Secret storage: - - not ready -
-
-
-
@@ -191,203 +107,6 @@ exports[` renders security section 1`] = `
-
-
-

- Cross-signing -

-
-
-
-
-
-
- - Advanced - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Cross-signing public keys: - - not found -
- Cross-signing private keys: - - not found in storage -
- Master private key: - - not found locally -
- Self signing private key: - - not found locally -
- User signing private key: - - not found locally -
- Homeserver feature support: - - not found -
-
-
-
-
-
-

- Cryptography -

-
-
-
- - - - - - - - - - - -
- Session ID: - - -
- Session key: - - - - ... - - -
-
-
-
- Export E2E room keys -
-
- Import E2E room keys -
-
-
- -
-
-
-
-
-