From 0757faef6d5a86bd79d656870c487ab1fe8e1790 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 23 Dec 2024 14:58:07 +0100 Subject: [PATCH 01/11] Make the encryption card more configurable: - Change the icon - Can set the destructive props --- .../settings/encryption/ChangeRecoveryKey.tsx | 8 ++++++- .../settings/encryption/EncryptionCard.tsx | 21 +++++++++++++------ .../encryption/EncryptionCard-test.tsx | 3 ++- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/components/views/settings/encryption/ChangeRecoveryKey.tsx b/src/components/views/settings/encryption/ChangeRecoveryKey.tsx index 7e02d7debde..0c2c9485f21 100644 --- a/src/components/views/settings/encryption/ChangeRecoveryKey.tsx +++ b/src/components/views/settings/encryption/ChangeRecoveryKey.tsx @@ -18,6 +18,7 @@ import { TextControl, } from "@vector-im/compound-web"; import CopyIcon from "@vector-im/compound-design-tokens/assets/web/icons/copy"; +import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key-solid"; import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../../languageHandler"; @@ -157,7 +158,12 @@ export function ChangeRecoveryKey({ pages={pages} onPageClick={onCancelClick} /> - + {content} diff --git a/src/components/views/settings/encryption/EncryptionCard.tsx b/src/components/views/settings/encryption/EncryptionCard.tsx index 8a10802cc3e..6f9ddd651a6 100644 --- a/src/components/views/settings/encryption/EncryptionCard.tsx +++ b/src/components/views/settings/encryption/EncryptionCard.tsx @@ -5,9 +5,8 @@ * Please see LICENSE files in the repository root for full details. */ -import React, { JSX, PropsWithChildren } from "react"; +import React, { JSX, PropsWithChildren, ComponentType, SVGAttributes } from "react"; import { BigIcon, Heading } from "@vector-im/compound-web"; -import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key-solid"; import classNames from "classnames"; interface EncryptionCardProps { @@ -22,7 +21,15 @@ interface EncryptionCardProps { /** * The description of the card. */ - description: string; + description?: string; + /** + * Whether this icon shows a destructive action. + */ + destructive?: boolean; + /** + * The icon to display. + */ + Icon: ComponentType>; } /** @@ -32,18 +39,20 @@ export function EncryptionCard({ title, description, className, + destructive = false, + Icon, children, }: PropsWithChildren): JSX.Element { return (
- - + + {title} - {description} + {description && {description}}
{children}
diff --git a/test/unit-tests/components/views/settings/encryption/EncryptionCard-test.tsx b/test/unit-tests/components/views/settings/encryption/EncryptionCard-test.tsx index d51fcb840bf..e6d9618a433 100644 --- a/test/unit-tests/components/views/settings/encryption/EncryptionCard-test.tsx +++ b/test/unit-tests/components/views/settings/encryption/EncryptionCard-test.tsx @@ -7,13 +7,14 @@ import React from "react"; import { render } from "jest-matrix-react"; +import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key-solid"; import { EncryptionCard } from "../../../../../../src/components/views/settings/encryption/EncryptionCard"; describe("", () => { it("should render", () => { const { asFragment } = render( - + Encryption card children , ); From 11db427649f7da28058e916abd4d8e24c84d21e7 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 23 Dec 2024 14:59:18 +0100 Subject: [PATCH 02/11] Update compound --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d79de6e081a..a188ab4adbe 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "@types/png-chunks-extract": "^1.0.2", "@types/react-virtualized": "^9.21.30", "@vector-im/compound-design-tokens": "^2.1.0", - "@vector-im/compound-web": "^7.5.0", + "@vector-im/compound-web": "^7.6.1", "@vector-im/matrix-wysiwyg": "2.38.0", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", diff --git a/yarn.lock b/yarn.lock index cbe07be2a19..f9d2a86b7e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3513,10 +3513,10 @@ resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-2.1.3.tgz#8205ffb455a09d71a02d838f3dbb8503c4e6ec27" integrity sha512-U4UF7MVguENf0lQnkU2a9p/3llTsLXzbzmFFOxi0h6ny2igNxZj/kROP/jXTxxV9xD4TNn3z098Bos4J/qJpBA== -"@vector-im/compound-web@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.5.0.tgz#1547af5f0ee27b94f79ab11eee006059f3d09707" - integrity sha512-Xhef8H5WrRmPuanzRBs8rnl+hwbcQnC7nKSCupUczAQ5hjlieBx4vcQYQ/nMkrs4rMGjgfFtR3E18wT5LlML/A== +"@vector-im/compound-web@^7.6.1": + version "7.6.1" + resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.6.1.tgz#c41fc8b2e4c5938041e1f0ff9792f8fbadd9ab87" + integrity sha512-LdHGFslkyky2aNPZwIOY9GgWn1VOUa2EBKHln8HBvpxnYPcs3/A2nb1+6SsJ7+Y0TzKc2HA0rZ3qPDhQ3hjZYQ== dependencies: "@floating-ui/react" "^0.27.0" "@radix-ui/react-context-menu" "^2.2.1" From e317b091179905f47ec0535edfcf0c1f6e7a599e Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 23 Dec 2024 14:59:30 +0100 Subject: [PATCH 03/11] Add advanced section --- res/css/_components.pcss | 2 + .../settings/encryption/_AdvancedPanel.pcss | 45 +++++++ .../encryption/_ResetIdentityPanel.pcss | 38 ++++++ .../settings/encryption/AdvancedPanel.tsx | 109 +++++++++++++++ .../encryption/ResetIdentityPanel.tsx | 125 ++++++++++++++++++ .../tabs/user/EncryptionUserSettingsTab.tsx | 24 +++- src/i18n/strings/en_EN.json | 15 +++ 7 files changed, 351 insertions(+), 7 deletions(-) create mode 100644 res/css/views/settings/encryption/_AdvancedPanel.pcss create mode 100644 res/css/views/settings/encryption/_ResetIdentityPanel.pcss create mode 100644 src/components/views/settings/encryption/AdvancedPanel.tsx create mode 100644 src/components/views/settings/encryption/ResetIdentityPanel.tsx diff --git a/res/css/_components.pcss b/res/css/_components.pcss index e0f9a5788de..bfab15625f2 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -353,8 +353,10 @@ @import "./views/settings/_ThemeChoicePanel.pcss"; @import "./views/settings/_UpdateCheckButton.pcss"; @import "./views/settings/_UserProfileSettings.pcss"; +@import "./views/settings/encryption/_AdvancedPanel.pcss"; @import "./views/settings/encryption/_ChangeRecoveryKey.pcss"; @import "./views/settings/encryption/_EncryptionCard.pcss"; +@import "./views/settings/encryption/_ResetIdentityPanel.pcss"; @import "./views/settings/tabs/_SettingsBanner.pcss"; @import "./views/settings/tabs/_SettingsIndent.pcss"; @import "./views/settings/tabs/_SettingsSection.pcss"; diff --git a/res/css/views/settings/encryption/_AdvancedPanel.pcss b/res/css/views/settings/encryption/_AdvancedPanel.pcss new file mode 100644 index 00000000000..46cccac5d44 --- /dev/null +++ b/res/css/views/settings/encryption/_AdvancedPanel.pcss @@ -0,0 +1,45 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * Please see LICENSE files in the repository root for full details. + */ + +.mx_AdvancedPanel_Details { + display: flex; + flex-direction: column; + gap: var(--cpd-space-6x); + width: 100%; + align-items: start; + + .mx_AdvancedPanel_Details_content { + display: flex; + flex-direction: column; + gap: var(--cpd-space-4x); + width: 100%; + + > span { + font: var(--cpd-font-body-lg-semibold); + padding-bottom: var(--cpd-space-2x); + border-bottom: 1px solid var(--cpd-color-gray-400); + } + + > div { + display: flex; + + > span { + width: 50%; + word-wrap: break-word; + } + } + + > div:nth-child(odd) { + background-color: var(--cpd-color-gray-200); + } + } + + .mx_AdvancedPanel_buttons { + display: flex; + gap: var(--cpd-space-4x); + } +} diff --git a/res/css/views/settings/encryption/_ResetIdentityPanel.pcss b/res/css/views/settings/encryption/_ResetIdentityPanel.pcss new file mode 100644 index 00000000000..b09d4f420cc --- /dev/null +++ b/res/css/views/settings/encryption/_ResetIdentityPanel.pcss @@ -0,0 +1,38 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * Please see LICENSE files in the repository root for full details. + */ + +.mx_ResetIdentityPanel { + .mx_ResetIdentityPanel_content { + display: flex; + flex-direction: column; + gap: var(--cpd-space-3x); + + > ul { + margin: 0; + list-style-type: none; + display: flex; + flex-direction: column; + gap: var(--cpd-space-1x); + + > li { + padding: var(--cpd-space-2x) var(--cpd-space-3x); + } + } + + > span { + font: var(--cpd-font-body-md-medium); + text-align: center; + } + } + + .mx_ResetIdentityPanel_footer { + display: flex; + flex-direction: column; + gap: var(--cpd-space-4x); + justify-content: center; + } +} diff --git a/src/components/views/settings/encryption/AdvancedPanel.tsx b/src/components/views/settings/encryption/AdvancedPanel.tsx new file mode 100644 index 00000000000..96b3031b3b5 --- /dev/null +++ b/src/components/views/settings/encryption/AdvancedPanel.tsx @@ -0,0 +1,109 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * Please see LICENSE files in the repository root for full details. + */ + +import React, { JSX, lazy, MouseEventHandler } from "react"; +import { Button, InlineSpinner } from "@vector-im/compound-web"; +import DownloadIcon from "@vector-im/compound-design-tokens/assets/web/icons/download"; +import ShareIcon from "@vector-im/compound-design-tokens/assets/web/icons/share"; + +import { _t } from "../../../../languageHandler"; +import { SettingsSection } from "../shared/SettingsSection"; +import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; +import { useAsyncMemo } from "../../../../hooks/useAsyncMemo"; +import Modal from "../../../../Modal"; + +interface AdvancedPanelProps { + /** + * Callback for when the user clicks the button to reset their identity. + */ + onResetIdentityClick: MouseEventHandler; +} + +/** + * The advanced panel of the encryption settings. + */ +export function AdvancedPanel({ onResetIdentityClick }: AdvancedPanelProps): JSX.Element { + return ( + + + + ); +} + +interface EncryptionDetails { + /** + * Callback for when the user clicks the button to reset their identity. + */ + onResetIdentityClick: MouseEventHandler; +} + +/** + * The encryption details section of the advanced panel. + */ +function EncryptionDetails({ onResetIdentityClick }: EncryptionDetails): JSX.Element { + const matrixClient = useMatrixClientContext(); + // Null when the keys are not loaded yet + const keys = useAsyncMemo( + () => { + const crypto = matrixClient.getCrypto(); + return crypto ? crypto.getOwnDeviceKeys() : Promise.resolve(null); + }, + [matrixClient], + null, + ); + + return ( +
+
+ {_t("settings|encryption|advanced|details_title")} +
+ {_t("settings|encryption|advanced|session_id")} + {matrixClient.deviceId} +
+
+ {_t("settings|encryption|advanced|session_key")} + {keys ? keys.ed25519 : } +
+
+
+ + +
+ +
+ ); +} diff --git a/src/components/views/settings/encryption/ResetIdentityPanel.tsx b/src/components/views/settings/encryption/ResetIdentityPanel.tsx new file mode 100644 index 00000000000..ece220372bd --- /dev/null +++ b/src/components/views/settings/encryption/ResetIdentityPanel.tsx @@ -0,0 +1,125 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * Please see LICENSE files in the repository root for full details. + */ + +import { Breadcrumb, Button, VisualList, VisualListItem } from "@vector-im/compound-web"; +import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check"; +import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info"; +import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error"; +import React, { MouseEventHandler } from "react"; +import { AuthDict, MatrixClient, UIAResponse } from "matrix-js-sdk/src/matrix"; + +import { _t } from "../../../../languageHandler"; +import { EncryptionCard } from "./EncryptionCard"; +import Modal from "../../../../Modal"; +import InteractiveAuthDialog from "../../dialogs/InteractiveAuthDialog"; +import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; +import { SSOAuthEntry } from "../../auth/InteractiveAuthEntryComponents"; + +interface ResetIdentityPanelProps { + /** + * Called when the identity is reset. + */ + onFinish: MouseEventHandler; + /** + * Called when the cancel button is clicked or when we go back in the breadcrumbs. + */ + onCancelClick: () => void; +} + +/** + * The panel for resetting the identity of the current user. + */ +export function ResetIdentityPanel({ onCancelClick, onFinish }: ResetIdentityPanelProps): JSX.Element { + const matrixClient = useMatrixClientContext(); + + return ( + <> + + +
+ + + {_t("settings|encryption|advanced|breadcrumb_first_description")} + + + {_t("settings|encryption|advanced|breadcrumb_second_description")} + + + {_t("settings|encryption|advanced|breadcrumb_third_description")} + + + {_t("settings|encryption|advanced|breadcrumb_warning")} +
+
+ + +
+
+ + ); +} + +/** + * Handles the UIA flow for resetting the identity. + * @param matrixClient + * @param makeRequest + */ +async function uiAuthCallback( + matrixClient: MatrixClient, + makeRequest: (authData: AuthDict) => Promise>, +): Promise { + const dialogAesthetics = { + [SSOAuthEntry.PHASE_PREAUTH]: { + title: _t("auth|uia|sso_title"), + body: _t("auth|uia|sso_preauth_body"), + continueText: _t("auth|sso"), + continueKind: "primary", + }, + [SSOAuthEntry.PHASE_POSTAUTH]: { + title: _t("encryption|confirm_encryption_setup_title"), + body: _t("encryption|confirm_encryption_setup_body"), + continueText: _t("action|confirm"), + continueKind: "primary", + }, + }; + + const { finished } = Modal.createDialog(InteractiveAuthDialog, { + title: _t("encryption|bootstrap_title"), + matrixClient, + makeRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, + }); + const [confirmed] = await finished; + if (!confirmed) { + throw new Error("Cross-signing key upload auth canceled"); + } +} diff --git a/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx b/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx index d4304eb4d41..4c5030cb58e 100644 --- a/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx @@ -6,7 +6,7 @@ */ import React, { JSX, useCallback, useEffect, useState } from "react"; -import { Button, InlineSpinner } from "@vector-im/compound-web"; +import { Button, InlineSpinner, Separator } from "@vector-im/compound-web"; import ComputerIcon from "@vector-im/compound-design-tokens/assets/web/icons/computer"; import SettingsTab from "../SettingsTab"; @@ -18,6 +18,8 @@ import Modal from "../../../../../Modal"; import SetupEncryptionDialog from "../../../dialogs/security/SetupEncryptionDialog"; import { SettingsSection } from "../../shared/SettingsSection"; import { SettingsSubheader } from "../../SettingsSubheader"; +import { AdvancedPanel } from "../../encryption/AdvancedPanel"; +import { ResetIdentityPanel } from "../../encryption/ResetIdentityPanel"; /** * The state in the encryption settings tab. @@ -29,8 +31,9 @@ import { SettingsSubheader } from "../../SettingsSubheader"; * This happens when the user has a recovery key and the user clicks on "Change recovery key" button of the RecoveryPanel. * - "set_recovery_key": The panel to show when the user is setting up their recovery key. * This happens when the user doesn't have a key a recovery key and the user clicks on "Set up recovery key" button of the RecoveryPanel. + * - "reset_identity": The panel to show when the user is resetting their identity. */ -type State = "loading" | "main" | "set_up_encryption" | "change_recovery_key" | "set_recovery_key"; +type State = "loading" | "main" | "set_up_encryption" | "change_recovery_key" | "set_recovery_key" | "reset_identity"; export function EncryptionUserSettingsTab(): JSX.Element { const [state, setState] = useState("loading"); @@ -46,11 +49,15 @@ export function EncryptionUserSettingsTab(): JSX.Element { break; case "main": content = ( - - setupNewKey ? setState("set_recovery_key") : setState("change_recovery_key") - } - /> + <> + + setupNewKey ? setState("set_recovery_key") : setState("change_recovery_key") + } + /> + + setState("reset_identity")} /> + ); break; case "change_recovery_key": @@ -63,6 +70,9 @@ export function EncryptionUserSettingsTab(): JSX.Element { /> ); break; + case "reset_identity": + content = setState("main")} onFinish={() => setState("main")} />; + break; } return ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 37a739a62e9..c255af05e8f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2418,6 +2418,21 @@ "enable_markdown": "Enable Markdown", "enable_markdown_description": "Start messages with /plain to send without markdown.", "encryption": { + "advanced": { + "breadcrumb_first_description": "Your account details, contacts, preferences, and chat list will be kept", + "breadcrumb_page": "Reset encryption", + "breadcrumb_second_description": "You will lose any message history that’s stored only on the server", + "breadcrumb_third_description": "You will need to verify all your existing devices and contacts again", + "breadcrumb_title": "Are you sure you want to reset your identity?", + "breadcrumb_warning": "Only do this if you believe your account has been compromised.", + "details_title": "Encryption details", + "export_keys": "Export keys", + "import_keys": "Import keys", + "reset_identity": "Reset cryptographic identity", + "session_id": "Session ID:", + "session_key": "Session key:", + "title": "Advanced" + }, "device_not_verified_button": "Verify this device", "device_not_verified_description": "You need to verify this device in order to view your encryption settings.", "device_not_verified_title": "Device not verified", From 54077902d4b1863c3ab18f4cc2d4344da0524cc7 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 23 Dec 2024 15:52:22 +0100 Subject: [PATCH 04/11] Add the `Never send encrypted messages to unverified devices` settings --- .../settings/encryption/_AdvancedPanel.pcss | 19 +++++---- .../settings/encryption/AdvancedPanel.tsx | 41 ++++++++++++++++++- src/i18n/strings/en_EN.json | 3 ++ 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/res/css/views/settings/encryption/_AdvancedPanel.pcss b/res/css/views/settings/encryption/_AdvancedPanel.pcss index 46cccac5d44..d99afe016a1 100644 --- a/res/css/views/settings/encryption/_AdvancedPanel.pcss +++ b/res/css/views/settings/encryption/_AdvancedPanel.pcss @@ -5,25 +5,30 @@ * Please see LICENSE files in the repository root for full details. */ -.mx_AdvancedPanel_Details { +.mx_AdvancedPanel_Details, +.mx_OtherSettings { display: flex; flex-direction: column; gap: var(--cpd-space-6x); width: 100%; align-items: start; + .mx_AdvancedPanel_Details_title, + .mx_OtherSettings_title { + font: var(--cpd-font-body-lg-semibold); + padding-bottom: var(--cpd-space-2x); + border-bottom: 1px solid var(--cpd-color-gray-400); + width: 100%; + } +} + +.mx_AdvancedPanel_Details { .mx_AdvancedPanel_Details_content { display: flex; flex-direction: column; gap: var(--cpd-space-4x); width: 100%; - > span { - font: var(--cpd-font-body-lg-semibold); - padding-bottom: var(--cpd-space-2x); - border-bottom: 1px solid var(--cpd-color-gray-400); - } - > div { display: flex; diff --git a/src/components/views/settings/encryption/AdvancedPanel.tsx b/src/components/views/settings/encryption/AdvancedPanel.tsx index 96b3031b3b5..4305dd0c0aa 100644 --- a/src/components/views/settings/encryption/AdvancedPanel.tsx +++ b/src/components/views/settings/encryption/AdvancedPanel.tsx @@ -6,7 +6,7 @@ */ import React, { JSX, lazy, MouseEventHandler } from "react"; -import { Button, InlineSpinner } from "@vector-im/compound-web"; +import { Button, HelpMessage, InlineField, InlineSpinner, Label, Root, ToggleControl } from "@vector-im/compound-web"; import DownloadIcon from "@vector-im/compound-design-tokens/assets/web/icons/download"; import ShareIcon from "@vector-im/compound-design-tokens/assets/web/icons/share"; @@ -15,6 +15,9 @@ import { SettingsSection } from "../shared/SettingsSection"; import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; import { useAsyncMemo } from "../../../../hooks/useAsyncMemo"; import Modal from "../../../../Modal"; +import { SettingLevel } from "../../../../settings/SettingLevel"; +import { useSettingValueAt } from "../../../../hooks/useSettings"; +import SettingsStore from "../../../../settings/SettingsStore"; interface AdvancedPanelProps { /** @@ -30,6 +33,7 @@ export function AdvancedPanel({ onResetIdentityClick }: AdvancedPanelProps): JSX return ( + ); } @@ -59,7 +63,9 @@ function EncryptionDetails({ onResetIdentityClick }: EncryptionDetails): JSX.Ele return (
- {_t("settings|encryption|advanced|details_title")} + + {_t("settings|encryption|advanced|details_title")} +
{_t("settings|encryption|advanced|session_id")} {matrixClient.deviceId} @@ -107,3 +113,34 @@ function EncryptionDetails({ onResetIdentityClick }: EncryptionDetails): JSX.Ele
); } + +/** + * Display the never send encrypted message to unverified devices setting. + */ +function OtherSettings(): JSX.Element | null { + const blacklistUnverifiedDevices = useSettingValueAt(SettingLevel.DEVICE, "blacklistUnverifiedDevices"); + const canSetValue = SettingsStore.canSetValue("blacklistUnverifiedDevices", null, SettingLevel.DEVICE); + if (!canSetValue) return null; + + return ( + { + const checked = new FormData(evt.currentTarget).get("neverSendEncrypted") === "on"; + await SettingsStore.setValue("blacklistUnverifiedDevices", null, SettingLevel.DEVICE, checked); + }} + > + + {_t("settings|encryption|advanced|other_people_device_title")} + + } + > + + {_t("settings|encryption|advanced|other_people_device_description")} + + + ); +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c255af05e8f..25735eceaee 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2428,6 +2428,9 @@ "details_title": "Encryption details", "export_keys": "Export keys", "import_keys": "Import keys", + "other_people_device_description": "By default in encrypted rooms, do not send encrypted messages to anyone until you’ve verified them", + "other_people_device_label": "Never send encrypted messages to unverified devices", + "other_people_device_title": "Other people’s devices", "reset_identity": "Reset cryptographic identity", "session_id": "Session ID:", "session_key": "Session key:", From 9c13d3df7bf438aebb9f2708fa6f08655c395c3d Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 8 Jan 2025 14:27:00 +0100 Subject: [PATCH 05/11] - Add commercial license - Remove generic type --- res/css/views/settings/encryption/_AdvancedPanel.pcss | 2 +- res/css/views/settings/encryption/_ResetIdentityPanel.pcss | 2 +- src/components/views/settings/encryption/AdvancedPanel.tsx | 4 ++-- .../views/settings/encryption/ResetIdentityPanel.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/res/css/views/settings/encryption/_AdvancedPanel.pcss b/res/css/views/settings/encryption/_AdvancedPanel.pcss index d99afe016a1..5202b46ae17 100644 --- a/res/css/views/settings/encryption/_AdvancedPanel.pcss +++ b/res/css/views/settings/encryption/_AdvancedPanel.pcss @@ -1,7 +1,7 @@ /* * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * 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. */ diff --git a/res/css/views/settings/encryption/_ResetIdentityPanel.pcss b/res/css/views/settings/encryption/_ResetIdentityPanel.pcss index b09d4f420cc..3c382990a27 100644 --- a/res/css/views/settings/encryption/_ResetIdentityPanel.pcss +++ b/res/css/views/settings/encryption/_ResetIdentityPanel.pcss @@ -1,7 +1,7 @@ /* * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * 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. */ diff --git a/src/components/views/settings/encryption/AdvancedPanel.tsx b/src/components/views/settings/encryption/AdvancedPanel.tsx index 4305dd0c0aa..d46f8770daf 100644 --- a/src/components/views/settings/encryption/AdvancedPanel.tsx +++ b/src/components/views/settings/encryption/AdvancedPanel.tsx @@ -1,7 +1,7 @@ /* * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * 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. */ @@ -118,7 +118,7 @@ function EncryptionDetails({ onResetIdentityClick }: EncryptionDetails): JSX.Ele * Display the never send encrypted message to unverified devices setting. */ function OtherSettings(): JSX.Element | null { - const blacklistUnverifiedDevices = useSettingValueAt(SettingLevel.DEVICE, "blacklistUnverifiedDevices"); + const blacklistUnverifiedDevices = useSettingValueAt(SettingLevel.DEVICE, "blacklistUnverifiedDevices"); const canSetValue = SettingsStore.canSetValue("blacklistUnverifiedDevices", null, SettingLevel.DEVICE); if (!canSetValue) return null; diff --git a/src/components/views/settings/encryption/ResetIdentityPanel.tsx b/src/components/views/settings/encryption/ResetIdentityPanel.tsx index ece220372bd..33f5cf1a4a9 100644 --- a/src/components/views/settings/encryption/ResetIdentityPanel.tsx +++ b/src/components/views/settings/encryption/ResetIdentityPanel.tsx @@ -1,7 +1,7 @@ /* * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * 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. */ From a5ccc9a9d39bb627396c7049619a6c8279eeb2bb Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 13 Jan 2025 10:46:31 +0100 Subject: [PATCH 06/11] Rename EncryptionDetails css classes --- res/css/views/settings/encryption/_AdvancedPanel.pcss | 10 +++++----- .../views/settings/encryption/AdvancedPanel.tsx | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/res/css/views/settings/encryption/_AdvancedPanel.pcss b/res/css/views/settings/encryption/_AdvancedPanel.pcss index 5202b46ae17..cf61bb1568b 100644 --- a/res/css/views/settings/encryption/_AdvancedPanel.pcss +++ b/res/css/views/settings/encryption/_AdvancedPanel.pcss @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -.mx_AdvancedPanel_Details, +.mx_EncryptionDetails, .mx_OtherSettings { display: flex; flex-direction: column; @@ -13,7 +13,7 @@ width: 100%; align-items: start; - .mx_AdvancedPanel_Details_title, + .mx_EncryptionDetails_session_title, .mx_OtherSettings_title { font: var(--cpd-font-body-lg-semibold); padding-bottom: var(--cpd-space-2x); @@ -22,8 +22,8 @@ } } -.mx_AdvancedPanel_Details { - .mx_AdvancedPanel_Details_content { +.mx_EncryptionDetails { + .mx_EncryptionDetails_session { display: flex; flex-direction: column; gap: var(--cpd-space-4x); @@ -43,7 +43,7 @@ } } - .mx_AdvancedPanel_buttons { + .mx_EncryptionDetails_buttons { display: flex; gap: var(--cpd-space-4x); } diff --git a/src/components/views/settings/encryption/AdvancedPanel.tsx b/src/components/views/settings/encryption/AdvancedPanel.tsx index d46f8770daf..856f27821c4 100644 --- a/src/components/views/settings/encryption/AdvancedPanel.tsx +++ b/src/components/views/settings/encryption/AdvancedPanel.tsx @@ -61,9 +61,9 @@ function EncryptionDetails({ onResetIdentityClick }: EncryptionDetails): JSX.Ele ); return ( -
-
- +
+
+ {_t("settings|encryption|advanced|details_title")}
@@ -75,7 +75,7 @@ function EncryptionDetails({ onResetIdentityClick }: EncryptionDetails): JSX.Ele {keys ? keys.ed25519 : }
-
+
+ +
+ +
+`; + +exports[` should display the device keys 1`] = ` +
+
+

+ Encryption details +

+
+ + Session ID: + + + ABCDEFGHI + +
+
+ + Session key: + + + ed25519 + +
+
+
+ + +
+ +
+`; + +exports[` should display the blacklist of unverified devices settings 1`] = ` +
+

+ Other people’s devices +

+
+
+
+ +
+
+
+
+ + + By default in encrypted rooms, do not send encrypted messages to anyone until you’ve verified them + +
+
+ +`; From 9f1099c275a8260e4da9df47ece7a5747f088b2f Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 16 Jan 2025 16:24:21 +0100 Subject: [PATCH 10/11] Add tests to `EncryptionUserSettingsTab` --- .../user/EncryptionUserSettingsTab-test.tsx | 15 ++ .../EncryptionUserSettingsTab-test.tsx.snap | 192 ++++++++++++++++++ 2 files changed, 207 insertions(+) diff --git a/test/unit-tests/components/views/settings/tabs/user/EncryptionUserSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/EncryptionUserSettingsTab-test.tsx index 49ce1404216..a1de62a32a4 100644 --- a/test/unit-tests/components/views/settings/tabs/user/EncryptionUserSettingsTab-test.tsx +++ b/test/unit-tests/components/views/settings/tabs/user/EncryptionUserSettingsTab-test.tsx @@ -94,4 +94,19 @@ describe("", () => { await waitFor(() => expect(screen.getByText("Set up recovery")).toBeInTheDocument()); expect(asFragment()).toMatchSnapshot(); }); + + it("should display the reset identity panel when the user clicks on the reset cryptographic identity panel", async () => { + const user = userEvent.setup(); + + const { asFragment } = renderComponent(); + await waitFor(() => { + const button = screen.getByRole("button", { name: "Reset cryptographic identity" }); + expect(button).toBeInTheDocument(); + user.click(button); + }); + await waitFor(() => + expect(screen.getByText("Are you sure you want to reset your identity?")).toBeInTheDocument(), + ); + expect(asFragment()).toMatchSnapshot(); + }); }); diff --git a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/EncryptionUserSettingsTab-test.tsx.snap b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/EncryptionUserSettingsTab-test.tsx.snap index 71ec4deb592..b460b91e512 100644 --- a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/EncryptionUserSettingsTab-test.tsx.snap +++ b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/EncryptionUserSettingsTab-test.tsx.snap @@ -81,6 +81,198 @@ exports[` should display the change recovery key pa `; +exports[` should display the reset identity panel when the user clicks on the reset cryptographic identity panel 1`] = ` + +
+
+ +
+
+
+ + + +
+

+ Are you sure you want to reset your identity? +

+
+
+
    +
  • + + Your account details, contacts, preferences, and chat list will be kept +
  • +
  • + + You will lose any message history that’s stored only on the server +
  • +
  • + + You will need to verify all your existing devices and contacts again +
  • +
+ + Only do this if you believe your account has been compromised. + +
+ +
+
+
+
+`; + exports[` should display the set up recovery key when the user clicks on the set up recovery key button 1`] = `
Date: Thu, 16 Jan 2025 16:34:23 +0100 Subject: [PATCH 11/11] Add tests to `ResetIdentityPanel` --- test/test-utils/test-utils.ts | 1 + .../encryption/ResetIdentityPanel-test.tsx | 37 ++++ .../ResetIdentityPanel-test.tsx.snap | 184 ++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 test/unit-tests/components/views/settings/encryption/ResetIdentityPanel-test.tsx create mode 100644 test/unit-tests/components/views/settings/encryption/__snapshots__/ResetIdentityPanel-test.tsx.snap diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 3be82e3ee86..b474a24aa81 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -151,6 +151,7 @@ export function createTestClient(): MatrixClient { }, }), isCrossSigningReady: jest.fn().mockResolvedValue(false), + resetEncryption: jest.fn(), }), getPushActionsForEvent: jest.fn(), diff --git a/test/unit-tests/components/views/settings/encryption/ResetIdentityPanel-test.tsx b/test/unit-tests/components/views/settings/encryption/ResetIdentityPanel-test.tsx new file mode 100644 index 00000000000..dc791a6a35c --- /dev/null +++ b/test/unit-tests/components/views/settings/encryption/ResetIdentityPanel-test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2025 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 from "react"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { render, screen } from "jest-matrix-react"; +import userEvent from "@testing-library/user-event"; + +import { ResetIdentityPanel } from "../../../../../../src/components/views/settings/encryption/ResetIdentityPanel"; +import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils"; + +describe("", () => { + let matrixClient: MatrixClient; + + beforeEach(() => { + matrixClient = createTestClient(); + }); + + it("should reset the encryption when the continue button is clicked", async () => { + const user = userEvent.setup(); + + const onFinish = jest.fn(); + const { asFragment } = render( + , + withClientContextRenderOptions(matrixClient), + ); + expect(asFragment()).toMatchSnapshot(); + + await user.click(screen.getByRole("button", { name: "Continue" })); + expect(matrixClient.getCrypto()!.resetEncryption).toHaveBeenCalled(); + expect(onFinish).toHaveBeenCalled(); + }); +}); diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/ResetIdentityPanel-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/ResetIdentityPanel-test.tsx.snap new file mode 100644 index 00000000000..7635ad06121 --- /dev/null +++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/ResetIdentityPanel-test.tsx.snap @@ -0,0 +1,184 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should reset the encryption when the continue button is clicked 1`] = ` + + +
+
+
+ + + +
+

+ Are you sure you want to reset your identity? +

+
+
+
    +
  • + + Your account details, contacts, preferences, and chat list will be kept +
  • +
  • + + You will lose any message history that’s stored only on the server +
  • +
  • + + You will need to verify all your existing devices and contacts again +
  • +
+ + Only do this if you believe your account has been compromised. + +
+ +
+
+`;