Add Advanced
section to the user settings encryption tab (#28804)
* Make the encryption card more configurable: - Change the icon - Can set the destructive props * Update compound * Add advanced section * Add the `Never send encrypted messages to unverified devices` settings * - Add commercial license - Remove generic type * Rename EncryptionDetails css classes * Use same uiAuthCallback * Use h3 for title * Add tests to `AdvancedPanel` * Add tests to `EncryptionUserSettingsTab` * Add tests to `ResetIdentityPanel` * Get only the recovery section in recovery tests * Add e2e test
@@ -91,7 +91,7 @@
|
|||||||
"@types/png-chunks-extract": "^1.0.2",
|
"@types/png-chunks-extract": "^1.0.2",
|
||||||
"@types/react-virtualized": "^9.21.30",
|
"@types/react-virtualized": "^9.21.30",
|
||||||
"@vector-im/compound-design-tokens": "^2.1.0",
|
"@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",
|
"@vector-im/matrix-wysiwyg": "2.38.0",
|
||||||
"@zxcvbn-ts/core": "^3.0.4",
|
"@zxcvbn-ts/core": "^3.0.4",
|
||||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||||
|
73
playwright/e2e/settings/encryption-user-tab/advanced.spec.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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 { test, expect } from "./index";
|
||||||
|
import { checkDeviceIsCrossSigned } from "../../crypto/utils";
|
||||||
|
import { bootstrapCrossSigningForClient } from "../../../pages/client";
|
||||||
|
|
||||||
|
test.describe("Advanced section in Encryption tab", () => {
|
||||||
|
test.beforeEach(async ({ page, app, homeserver, credentials, util }) => {
|
||||||
|
const clientHandle = await app.client.prepareClient();
|
||||||
|
// Reset cross signing in order to have a verified session
|
||||||
|
await bootstrapCrossSigningForClient(clientHandle, credentials, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should show the encryption details", { tag: "@screenshot" }, async ({ page, app, util }) => {
|
||||||
|
await util.openEncryptionTab();
|
||||||
|
const section = util.getEncryptionDetailsSection();
|
||||||
|
|
||||||
|
const deviceId = await page.evaluate(() => window.mxMatrixClientPeg.get().getDeviceId());
|
||||||
|
await expect(section.getByText(deviceId)).toBeVisible();
|
||||||
|
|
||||||
|
await expect(section).toMatchScreenshot("encryption-details.png", {
|
||||||
|
mask: [section.getByTestId("deviceId"), section.getByTestId("sessionKey")],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should show the import room keys dialog", async ({ page, app, util }) => {
|
||||||
|
await util.openEncryptionTab();
|
||||||
|
const section = util.getEncryptionDetailsSection();
|
||||||
|
|
||||||
|
await section.getByRole("button", { name: "Import keys" }).click();
|
||||||
|
await expect(page.getByRole("heading", { name: "Import room keys" })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should show the export room keys dialog", async ({ page, app, util }) => {
|
||||||
|
await util.openEncryptionTab();
|
||||||
|
const section = util.getEncryptionDetailsSection();
|
||||||
|
|
||||||
|
await section.getByRole("button", { name: "Export keys" }).click();
|
||||||
|
await expect(page.getByRole("heading", { name: "Export room keys" })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"should reset the cryptographic identity",
|
||||||
|
{ tag: "@screenshot" },
|
||||||
|
async ({ page, app, credentials, util }) => {
|
||||||
|
const tab = await util.openEncryptionTab();
|
||||||
|
const section = util.getEncryptionDetailsSection();
|
||||||
|
|
||||||
|
await section.getByRole("button", { name: "Reset cryptographic identity" }).click();
|
||||||
|
await expect(util.getEncryptionTabContent()).toMatchScreenshot("reset-cryptographic-identity.png");
|
||||||
|
await tab.getByRole("button", { name: "Continue" }).click();
|
||||||
|
|
||||||
|
// Fill password dialog and validate
|
||||||
|
const dialog = page.locator(".mx_InteractiveAuthDialog");
|
||||||
|
await dialog.getByRole("textbox", { name: "Password" }).fill(credentials.password);
|
||||||
|
await dialog.getByRole("button", { name: "Continue" }).click();
|
||||||
|
|
||||||
|
await expect(section.getByRole("button", { name: "Reset cryptographic identity" })).toBeVisible();
|
||||||
|
|
||||||
|
// After resetting the identity, the user should set up a new recovery key
|
||||||
|
await expect(
|
||||||
|
util.getEncryptionRecoverySection().getByRole("button", { name: "Set up recovery" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await checkDeviceIsCrossSigned(app);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
@@ -18,6 +18,8 @@ export { expect };
|
|||||||
export const test = base.extend<{
|
export const test = base.extend<{
|
||||||
util: Helpers;
|
util: Helpers;
|
||||||
}>({
|
}>({
|
||||||
|
displayName: "Alice",
|
||||||
|
|
||||||
util: async ({ page, app, bot }, use) => {
|
util: async ({ page, app, bot }, use) => {
|
||||||
await use(new Helpers(page, app));
|
await use(new Helpers(page, app));
|
||||||
},
|
},
|
||||||
@@ -67,6 +69,20 @@ class Helpers {
|
|||||||
return this.page.getByTestId("encryptionTab");
|
return this.page.getByTestId("encryptionTab");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the recovery section
|
||||||
|
*/
|
||||||
|
getEncryptionRecoverySection() {
|
||||||
|
return this.page.getByTestId("recoveryPanel");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the encryption details section
|
||||||
|
*/
|
||||||
|
getEncryptionDetailsSection() {
|
||||||
|
return this.page.getByTestId("encryptionDetails");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the default key id of the secret storage to `null`
|
* Set the default key id of the secret storage to `null`
|
||||||
*/
|
*/
|
||||||
@@ -92,6 +108,6 @@ class Helpers {
|
|||||||
const clipboardContent = await this.app.getClipboard();
|
const clipboardContent = await this.app.getClipboard();
|
||||||
await dialog.getByRole("textbox").fill(clipboardContent);
|
await dialog.getByRole("textbox").fill(clipboardContent);
|
||||||
await dialog.getByRole("button", { name: confirmButtonLabel }).click();
|
await dialog.getByRole("button", { name: confirmButtonLabel }).click();
|
||||||
await expect(dialog).toMatchScreenshot("default-recovery.png");
|
await expect(this.getEncryptionRecoverySection()).toMatchScreenshot("default-recovery.png");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,15 +32,19 @@ test.describe("Recovery section in Encryption tab", () => {
|
|||||||
|
|
||||||
test("should verify the device", { tag: "@screenshot" }, async ({ page, app, util }) => {
|
test("should verify the device", { tag: "@screenshot" }, async ({ page, app, util }) => {
|
||||||
const dialog = await util.openEncryptionTab();
|
const dialog = await util.openEncryptionTab();
|
||||||
|
const content = util.getEncryptionTabContent();
|
||||||
|
|
||||||
// The user's device is in an unverified state, therefore the only option available to them here is to verify it
|
// The user's device is in an unverified state, therefore the only option available to them here is to verify it
|
||||||
const verifyButton = dialog.getByRole("button", { name: "Verify this device" });
|
const verifyButton = dialog.getByRole("button", { name: "Verify this device" });
|
||||||
await expect(verifyButton).toBeVisible();
|
await expect(verifyButton).toBeVisible();
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("verify-device-encryption-tab.png");
|
await expect(content).toMatchScreenshot("verify-device-encryption-tab.png");
|
||||||
await verifyButton.click();
|
await verifyButton.click();
|
||||||
|
|
||||||
await util.verifyDevice(recoveryKey);
|
await util.verifyDevice(recoveryKey);
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("default-recovery.png");
|
|
||||||
|
await expect(content).toMatchScreenshot("default-tab.png", {
|
||||||
|
mask: [content.getByTestId("deviceId"), content.getByTestId("sessionKey")],
|
||||||
|
});
|
||||||
|
|
||||||
// Check that our device is now cross-signed
|
// Check that our device is now cross-signed
|
||||||
await checkDeviceIsCrossSigned(app);
|
await checkDeviceIsCrossSigned(app);
|
||||||
@@ -61,7 +65,7 @@ test.describe("Recovery section in Encryption tab", () => {
|
|||||||
// The user can only change the recovery key
|
// The user can only change the recovery key
|
||||||
const changeButton = dialog.getByRole("button", { name: "Change recovery key" });
|
const changeButton = dialog.getByRole("button", { name: "Change recovery key" });
|
||||||
await expect(changeButton).toBeVisible();
|
await expect(changeButton).toBeVisible();
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("default-recovery.png");
|
await expect(util.getEncryptionRecoverySection()).toMatchScreenshot("default-recovery.png");
|
||||||
await changeButton.click();
|
await changeButton.click();
|
||||||
|
|
||||||
// Display the new recovery key and click on the copy button
|
// Display the new recovery key and click on the copy button
|
||||||
@@ -89,7 +93,7 @@ test.describe("Recovery section in Encryption tab", () => {
|
|||||||
const dialog = await util.openEncryptionTab();
|
const dialog = await util.openEncryptionTab();
|
||||||
const setupButton = dialog.getByRole("button", { name: "Set up recovery" });
|
const setupButton = dialog.getByRole("button", { name: "Set up recovery" });
|
||||||
await expect(setupButton).toBeVisible();
|
await expect(setupButton).toBeVisible();
|
||||||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("set-up-recovery.png");
|
await expect(util.getEncryptionRecoverySection()).toMatchScreenshot("set-up-recovery.png");
|
||||||
await setupButton.click();
|
await setupButton.click();
|
||||||
|
|
||||||
// Display an informative panel about the recovery key
|
// Display an informative panel about the recovery key
|
||||||
@@ -137,12 +141,12 @@ test.describe("Recovery section in Encryption tab", () => {
|
|||||||
const dialog = util.getEncryptionTabContent();
|
const dialog = util.getEncryptionTabContent();
|
||||||
const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" });
|
const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" });
|
||||||
await expect(enterKeyButton).toBeVisible();
|
await expect(enterKeyButton).toBeVisible();
|
||||||
await expect(dialog).toMatchScreenshot("out-of-sync-recovery.png");
|
await expect(util.getEncryptionRecoverySection()).toMatchScreenshot("out-of-sync-recovery.png");
|
||||||
await enterKeyButton.click();
|
await enterKeyButton.click();
|
||||||
|
|
||||||
// Fill the recovery key
|
// Fill the recovery key
|
||||||
await util.enterRecoveryKey(recoveryKey);
|
await util.enterRecoveryKey(recoveryKey);
|
||||||
await expect(dialog).toMatchScreenshot("default-recovery.png");
|
await expect(util.getEncryptionRecoverySection()).toMatchScreenshot("default-recovery.png");
|
||||||
|
|
||||||
// Check that our device is now cross-signed
|
// Check that our device is now cross-signed
|
||||||
await checkDeviceIsCrossSigned(app);
|
await checkDeviceIsCrossSigned(app);
|
||||||
|
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 16 KiB |
@@ -354,8 +354,10 @@
|
|||||||
@import "./views/settings/_ThemeChoicePanel.pcss";
|
@import "./views/settings/_ThemeChoicePanel.pcss";
|
||||||
@import "./views/settings/_UpdateCheckButton.pcss";
|
@import "./views/settings/_UpdateCheckButton.pcss";
|
||||||
@import "./views/settings/_UserProfileSettings.pcss";
|
@import "./views/settings/_UserProfileSettings.pcss";
|
||||||
|
@import "./views/settings/encryption/_AdvancedPanel.pcss";
|
||||||
@import "./views/settings/encryption/_ChangeRecoveryKey.pcss";
|
@import "./views/settings/encryption/_ChangeRecoveryKey.pcss";
|
||||||
@import "./views/settings/encryption/_EncryptionCard.pcss";
|
@import "./views/settings/encryption/_EncryptionCard.pcss";
|
||||||
|
@import "./views/settings/encryption/_ResetIdentityPanel.pcss";
|
||||||
@import "./views/settings/tabs/_SettingsBanner.pcss";
|
@import "./views/settings/tabs/_SettingsBanner.pcss";
|
||||||
@import "./views/settings/tabs/_SettingsIndent.pcss";
|
@import "./views/settings/tabs/_SettingsIndent.pcss";
|
||||||
@import "./views/settings/tabs/_SettingsSection.pcss";
|
@import "./views/settings/tabs/_SettingsSection.pcss";
|
||||||
|
51
res/css/views/settings/encryption/_AdvancedPanel.pcss
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 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_EncryptionDetails,
|
||||||
|
.mx_OtherSettings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--cpd-space-6x);
|
||||||
|
width: 100%;
|
||||||
|
align-items: start;
|
||||||
|
|
||||||
|
.mx_EncryptionDetails_session_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%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EncryptionDetails {
|
||||||
|
.mx_EncryptionDetails_session {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--cpd-space-4x);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
width: 50%;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(odd) {
|
||||||
|
background-color: var(--cpd-color-gray-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EncryptionDetails_buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--cpd-space-4x);
|
||||||
|
}
|
||||||
|
}
|
26
res/css/views/settings/encryption/_ResetIdentityPanel.pcss
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 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_ResetIdentityPanel {
|
||||||
|
.mx_ResetIdentityPanel_content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -31,9 +31,15 @@ export async function createCrossSigning(cli: MatrixClient): Promise<void> {
|
|||||||
throw new Error("No crypto API found!");
|
throw new Error("No crypto API found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
const doBootstrapUIAuth = async (
|
await cryptoApi.bootstrapCrossSigning({
|
||||||
|
authUploadDeviceSigningKeys: (makeRequest) => uiAuthCallback(cli, makeRequest),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function uiAuthCallback(
|
||||||
|
matrixClient: MatrixClient,
|
||||||
makeRequest: (authData: AuthDict) => Promise<UIAResponse<void>>,
|
makeRequest: (authData: AuthDict) => Promise<UIAResponse<void>>,
|
||||||
): Promise<void> => {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await makeRequest({});
|
await makeRequest({});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -59,7 +65,7 @@ export async function createCrossSigning(cli: MatrixClient): Promise<void> {
|
|||||||
|
|
||||||
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
||||||
title: _t("encryption|bootstrap_title"),
|
title: _t("encryption|bootstrap_title"),
|
||||||
matrixClient: cli,
|
matrixClient,
|
||||||
makeRequest,
|
makeRequest,
|
||||||
aestheticsForStagePhases: {
|
aestheticsForStagePhases: {
|
||||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
@@ -71,9 +77,4 @@ export async function createCrossSigning(cli: MatrixClient): Promise<void> {
|
|||||||
throw new Error("Cross-signing key upload auth canceled");
|
throw new Error("Cross-signing key upload auth canceled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
await cryptoApi.bootstrapCrossSigning({
|
|
||||||
authUploadDeviceSigningKeys: doBootstrapUIAuth,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
139
src/components/views/settings/encryption/AdvancedPanel.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 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, { JSX, lazy, MouseEventHandler } from "react";
|
||||||
|
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";
|
||||||
|
|
||||||
|
import { _t } from "../../../../languageHandler";
|
||||||
|
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 {
|
||||||
|
/**
|
||||||
|
* Callback for when the user clicks the button to reset their identity.
|
||||||
|
*/
|
||||||
|
onResetIdentityClick: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The advanced panel of the encryption settings.
|
||||||
|
*/
|
||||||
|
export function AdvancedPanel({ onResetIdentityClick }: AdvancedPanelProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<SettingsSection heading={_t("settings|encryption|advanced|title")} legacy={false}>
|
||||||
|
<EncryptionDetails onResetIdentityClick={onResetIdentityClick} />
|
||||||
|
<OtherSettings />
|
||||||
|
</SettingsSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EncryptionDetails {
|
||||||
|
/**
|
||||||
|
* Callback for when the user clicks the button to reset their identity.
|
||||||
|
*/
|
||||||
|
onResetIdentityClick: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(() => matrixClient.getCrypto()!.getOwnDeviceKeys(), [matrixClient], null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx_EncryptionDetails" data-testid="encryptionDetails">
|
||||||
|
<div className="mx_EncryptionDetails_session">
|
||||||
|
<h3 className="mx_EncryptionDetails_session_title">
|
||||||
|
{_t("settings|encryption|advanced|details_title")}
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
<span>{_t("settings|encryption|advanced|session_id")}</span>
|
||||||
|
<span data-testid="deviceId">{matrixClient.deviceId}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{_t("settings|encryption|advanced|session_key")}</span>
|
||||||
|
<span data-testid="sessionKey">
|
||||||
|
{keys ? keys.ed25519 : <InlineSpinner aria-label={_t("common|loading")} />}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mx_EncryptionDetails_buttons">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
kind="secondary"
|
||||||
|
Icon={ShareIcon}
|
||||||
|
onClick={() =>
|
||||||
|
Modal.createDialog(
|
||||||
|
lazy(
|
||||||
|
() => import("../../../../async-components/views/dialogs/security/ExportE2eKeysDialog"),
|
||||||
|
),
|
||||||
|
{ matrixClient },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{_t("settings|encryption|advanced|export_keys")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
kind="secondary"
|
||||||
|
Icon={DownloadIcon}
|
||||||
|
onClick={() =>
|
||||||
|
Modal.createDialog(
|
||||||
|
lazy(
|
||||||
|
() => import("../../../../async-components/views/dialogs/security/ImportE2eKeysDialog"),
|
||||||
|
),
|
||||||
|
{ matrixClient },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{_t("settings|encryption|advanced|import_keys")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Button size="sm" kind="tertiary" destructive={true} onClick={onResetIdentityClick}>
|
||||||
|
{_t("settings|encryption|advanced|reset_identity")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 (
|
||||||
|
<Root
|
||||||
|
data-testid="otherSettings"
|
||||||
|
className="mx_OtherSettings"
|
||||||
|
onChange={async (evt) => {
|
||||||
|
const checked = new FormData(evt.currentTarget).get("neverSendEncrypted") === "on";
|
||||||
|
await SettingsStore.setValue("blacklistUnverifiedDevices", null, SettingLevel.DEVICE, checked);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3 className="mx_OtherSettings_title">{_t("settings|encryption|advanced|other_people_device_title")}</h3>
|
||||||
|
<InlineField
|
||||||
|
name="neverSendEncrypted"
|
||||||
|
control={<ToggleControl name="neverSendEncrypted" defaultChecked={blacklistUnverifiedDevices} />}
|
||||||
|
>
|
||||||
|
<Label>{_t("settings|encryption|advanced|other_people_device_label")}</Label>
|
||||||
|
<HelpMessage>{_t("settings|encryption|advanced|other_people_device_description")}</HelpMessage>
|
||||||
|
</InlineField>
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
|
}
|
@@ -18,6 +18,7 @@ import {
|
|||||||
TextControl,
|
TextControl,
|
||||||
} from "@vector-im/compound-web";
|
} from "@vector-im/compound-web";
|
||||||
import CopyIcon from "@vector-im/compound-design-tokens/assets/web/icons/copy";
|
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 { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
@@ -157,7 +158,12 @@ export function ChangeRecoveryKey({
|
|||||||
pages={pages}
|
pages={pages}
|
||||||
onPageClick={onCancelClick}
|
onPageClick={onCancelClick}
|
||||||
/>
|
/>
|
||||||
<EncryptionCard title={labels.title} description={labels.description} className="mx_ChangeRecoveryKey">
|
<EncryptionCard
|
||||||
|
Icon={KeyIcon}
|
||||||
|
title={labels.title}
|
||||||
|
description={labels.description}
|
||||||
|
className="mx_ChangeRecoveryKey"
|
||||||
|
>
|
||||||
{content}
|
{content}
|
||||||
</EncryptionCard>
|
</EncryptionCard>
|
||||||
</>
|
</>
|
||||||
|
@@ -5,9 +5,8 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* 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 { BigIcon, Heading } from "@vector-im/compound-web";
|
||||||
import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key-solid";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
interface EncryptionCardProps {
|
interface EncryptionCardProps {
|
||||||
@@ -22,7 +21,15 @@ interface EncryptionCardProps {
|
|||||||
/**
|
/**
|
||||||
* The description of the card.
|
* The description of the card.
|
||||||
*/
|
*/
|
||||||
description: string;
|
description?: string;
|
||||||
|
/**
|
||||||
|
* Whether this icon shows a destructive action.
|
||||||
|
*/
|
||||||
|
destructive?: boolean;
|
||||||
|
/**
|
||||||
|
* The icon to display.
|
||||||
|
*/
|
||||||
|
Icon: ComponentType<SVGAttributes<SVGElement>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,18 +39,20 @@ export function EncryptionCard({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
className,
|
className,
|
||||||
|
destructive = false,
|
||||||
|
Icon,
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren<EncryptionCardProps>): JSX.Element {
|
}: PropsWithChildren<EncryptionCardProps>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className={classNames("mx_EncryptionCard", className)}>
|
<div className={classNames("mx_EncryptionCard", className)}>
|
||||||
<div className="mx_EncryptionCard_header">
|
<div className="mx_EncryptionCard_header">
|
||||||
<BigIcon>
|
<BigIcon destructive={destructive}>
|
||||||
<KeyIcon />
|
<Icon />
|
||||||
</BigIcon>
|
</BigIcon>
|
||||||
<Heading as="h2" size="sm" weight="semibold">
|
<Heading as="h2" size="sm" weight="semibold">
|
||||||
{title}
|
{title}
|
||||||
</Heading>
|
</Heading>
|
||||||
<span>{description}</span>
|
{description && <span>{description}</span>}
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -106,6 +106,7 @@ export function RecoveryPanel({ onChangeRecoveryKeyClick }: RecoveryPanelProps):
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
subHeading={<Subheader state={state} />}
|
subHeading={<Subheader state={state} />}
|
||||||
|
data-testid="recoveryPanel"
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 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 { 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 { _t } from "../../../../languageHandler";
|
||||||
|
import { EncryptionCard } from "./EncryptionCard";
|
||||||
|
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
|
||||||
|
import { uiAuthCallback } from "../../../../CreateCrossSigning";
|
||||||
|
|
||||||
|
interface ResetIdentityPanelProps {
|
||||||
|
/**
|
||||||
|
* Called when the identity is reset.
|
||||||
|
*/
|
||||||
|
onFinish: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
/**
|
||||||
|
* 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 (
|
||||||
|
<>
|
||||||
|
<Breadcrumb
|
||||||
|
backLabel={_t("action|back")}
|
||||||
|
onBackClick={onCancelClick}
|
||||||
|
pages={[_t("settings|encryption|title"), _t("settings|encryption|advanced|breadcrumb_page")]}
|
||||||
|
onPageClick={onCancelClick}
|
||||||
|
/>
|
||||||
|
<EncryptionCard
|
||||||
|
Icon={ErrorIcon}
|
||||||
|
destructive={true}
|
||||||
|
title={_t("settings|encryption|advanced|breadcrumb_title")}
|
||||||
|
className="mx_ResetIdentityPanel"
|
||||||
|
>
|
||||||
|
<div className="mx_ResetIdentityPanel_content">
|
||||||
|
<VisualList>
|
||||||
|
<VisualListItem Icon={CheckIcon} success={true}>
|
||||||
|
{_t("settings|encryption|advanced|breadcrumb_first_description")}
|
||||||
|
</VisualListItem>
|
||||||
|
<VisualListItem Icon={InfoIcon}>
|
||||||
|
{_t("settings|encryption|advanced|breadcrumb_second_description")}
|
||||||
|
</VisualListItem>
|
||||||
|
<VisualListItem Icon={InfoIcon}>
|
||||||
|
{_t("settings|encryption|advanced|breadcrumb_third_description")}
|
||||||
|
</VisualListItem>
|
||||||
|
</VisualList>
|
||||||
|
<span>{_t("settings|encryption|advanced|breadcrumb_warning")}</span>
|
||||||
|
</div>
|
||||||
|
<div className="mx_ResetIdentityPanel_footer">
|
||||||
|
<Button
|
||||||
|
destructive={true}
|
||||||
|
onClick={async (evt) => {
|
||||||
|
await matrixClient
|
||||||
|
.getCrypto()
|
||||||
|
?.resetEncryption((makeRequest) => uiAuthCallback(matrixClient, makeRequest));
|
||||||
|
onFinish(evt);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{_t("action|continue")}
|
||||||
|
</Button>
|
||||||
|
<Button kind="tertiary" onClick={onCancelClick}>
|
||||||
|
{_t("action|cancel")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</EncryptionCard>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { JSX, useCallback, useEffect, useState } from "react";
|
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 ComputerIcon from "@vector-im/compound-design-tokens/assets/web/icons/computer";
|
||||||
|
|
||||||
import SettingsTab from "../SettingsTab";
|
import SettingsTab from "../SettingsTab";
|
||||||
@@ -18,6 +18,8 @@ import Modal from "../../../../../Modal";
|
|||||||
import SetupEncryptionDialog from "../../../dialogs/security/SetupEncryptionDialog";
|
import SetupEncryptionDialog from "../../../dialogs/security/SetupEncryptionDialog";
|
||||||
import { SettingsSection } from "../../shared/SettingsSection";
|
import { SettingsSection } from "../../shared/SettingsSection";
|
||||||
import { SettingsSubheader } from "../../SettingsSubheader";
|
import { SettingsSubheader } from "../../SettingsSubheader";
|
||||||
|
import { AdvancedPanel } from "../../encryption/AdvancedPanel";
|
||||||
|
import { ResetIdentityPanel } from "../../encryption/ResetIdentityPanel";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state in the encryption settings tab.
|
* 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.
|
* 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.
|
* - "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.
|
* 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 {
|
export function EncryptionUserSettingsTab(): JSX.Element {
|
||||||
const [state, setState] = useState<State>("loading");
|
const [state, setState] = useState<State>("loading");
|
||||||
@@ -46,11 +49,15 @@ export function EncryptionUserSettingsTab(): JSX.Element {
|
|||||||
break;
|
break;
|
||||||
case "main":
|
case "main":
|
||||||
content = (
|
content = (
|
||||||
|
<>
|
||||||
<RecoveryPanel
|
<RecoveryPanel
|
||||||
onChangeRecoveryKeyClick={(setupNewKey) =>
|
onChangeRecoveryKeyClick={(setupNewKey) =>
|
||||||
setupNewKey ? setState("set_recovery_key") : setState("change_recovery_key")
|
setupNewKey ? setState("set_recovery_key") : setState("change_recovery_key")
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Separator kind="section" />
|
||||||
|
<AdvancedPanel onResetIdentityClick={() => setState("reset_identity")} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "change_recovery_key":
|
case "change_recovery_key":
|
||||||
@@ -63,6 +70,9 @@ export function EncryptionUserSettingsTab(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "reset_identity":
|
||||||
|
content = <ResetIdentityPanel onCancelClick={() => setState("main")} onFinish={() => setState("main")} />;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -2422,6 +2422,24 @@
|
|||||||
"enable_markdown": "Enable Markdown",
|
"enable_markdown": "Enable Markdown",
|
||||||
"enable_markdown_description": "Start messages with <code>/plain</code> to send without markdown.",
|
"enable_markdown_description": "Start messages with <code>/plain</code> to send without markdown.",
|
||||||
"encryption": {
|
"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",
|
||||||
|
"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:",
|
||||||
|
"title": "Advanced"
|
||||||
|
},
|
||||||
"device_not_verified_button": "Verify this device",
|
"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_description": "You need to verify this device in order to view your encryption settings.",
|
||||||
"device_not_verified_title": "Device not verified",
|
"device_not_verified_title": "Device not verified",
|
||||||
|
@@ -116,7 +116,7 @@ export function createTestClient(): MatrixClient {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getCrypto: jest.fn().mockReturnValue({
|
getCrypto: jest.fn().mockReturnValue({
|
||||||
getOwnDeviceKeys: jest.fn(),
|
getOwnDeviceKeys: jest.fn().mockResolvedValue({ ed25519: "ed25519", curve25519: "curve25519" }),
|
||||||
getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
|
getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
|
||||||
getUserVerificationStatus: jest.fn(),
|
getUserVerificationStatus: jest.fn(),
|
||||||
getDeviceVerificationStatus: jest.fn(),
|
getDeviceVerificationStatus: jest.fn(),
|
||||||
@@ -151,6 +151,7 @@ export function createTestClient(): MatrixClient {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
isCrossSigningReady: jest.fn().mockResolvedValue(false),
|
isCrossSigningReady: jest.fn().mockResolvedValue(false),
|
||||||
|
resetEncryption: jest.fn(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getPushActionsForEvent: jest.fn(),
|
getPushActionsForEvent: jest.fn(),
|
||||||
|
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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, waitFor } from "jest-matrix-react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
|
import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
|
||||||
|
import { AdvancedPanel } from "../../../../../../src/components/views/settings/encryption/AdvancedPanel";
|
||||||
|
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||||
|
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
|
||||||
|
|
||||||
|
describe("<AdvancedPanel />", () => {
|
||||||
|
let matrixClient: MatrixClient;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
matrixClient = createTestClient();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function renderAdvancedPanel(onResetIdentityClick = jest.fn()) {
|
||||||
|
const renderResult = render(
|
||||||
|
<AdvancedPanel onResetIdentityClick={onResetIdentityClick} />,
|
||||||
|
withClientContextRenderOptions(matrixClient),
|
||||||
|
);
|
||||||
|
// Wait for the device keys to be displayed
|
||||||
|
await waitFor(() => expect(screen.getByText("ed25519")).toBeInTheDocument());
|
||||||
|
return renderResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("<EncryptionDetails />", () => {
|
||||||
|
it("should display a spinner when loading the device keys", async () => {
|
||||||
|
jest.spyOn(matrixClient.getCrypto()!, "getOwnDeviceKeys").mockImplementation(() => new Promise(() => {}));
|
||||||
|
render(<AdvancedPanel onResetIdentityClick={jest.fn()} />, withClientContextRenderOptions(matrixClient));
|
||||||
|
|
||||||
|
expect(screen.getByTestId("encryptionDetails")).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display the device keys", async () => {
|
||||||
|
await renderAdvancedPanel();
|
||||||
|
|
||||||
|
// session id
|
||||||
|
expect(screen.getByText("ABCDEFGHI")).toBeInTheDocument();
|
||||||
|
// session key
|
||||||
|
expect(screen.getByText("ed25519")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("encryptionDetails")).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call the onResetIdentityClick callback when the reset cryptographic identity button is clicked", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
const onResetIdentityClick = jest.fn();
|
||||||
|
await renderAdvancedPanel(onResetIdentityClick);
|
||||||
|
|
||||||
|
const resetIdentityButton = screen.getByRole("button", { name: "Reset cryptographic identity" });
|
||||||
|
await user.click(resetIdentityButton);
|
||||||
|
|
||||||
|
expect(onResetIdentityClick).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("<OtherSettings />", () => {
|
||||||
|
it("should display the blacklist of unverified devices settings", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(true);
|
||||||
|
jest.spyOn(SettingsStore, "canSetValue").mockReturnValue(true);
|
||||||
|
jest.spyOn(SettingsStore, "setValue");
|
||||||
|
|
||||||
|
await renderAdvancedPanel();
|
||||||
|
|
||||||
|
expect(screen.getByTestId("otherSettings")).toMatchSnapshot();
|
||||||
|
const checkbox = screen.getByRole("checkbox", {
|
||||||
|
name: "Never send encrypted messages to unverified devices",
|
||||||
|
});
|
||||||
|
expect(checkbox).toBeChecked();
|
||||||
|
|
||||||
|
await user.click(checkbox);
|
||||||
|
expect(SettingsStore.setValue).toHaveBeenCalledWith(
|
||||||
|
"blacklistUnverifiedDevices",
|
||||||
|
null,
|
||||||
|
SettingLevel.DEVICE,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not display the section when the user can not set the value", async () => {
|
||||||
|
jest.spyOn(SettingsStore, "canSetValue").mockReturnValue(false);
|
||||||
|
jest.spyOn(SettingsStore, "setValue");
|
||||||
|
|
||||||
|
await renderAdvancedPanel();
|
||||||
|
expect(screen.queryByTestId("otherSettings")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -7,13 +7,14 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { render } from "jest-matrix-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";
|
import { EncryptionCard } from "../../../../../../src/components/views/settings/encryption/EncryptionCard";
|
||||||
|
|
||||||
describe("<EncryptionCard />", () => {
|
describe("<EncryptionCard />", () => {
|
||||||
it("should render", () => {
|
it("should render", () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<EncryptionCard title="My title" description="My description">
|
<EncryptionCard Icon={KeyIcon} title="My title" description="My description">
|
||||||
Encryption card children
|
Encryption card children
|
||||||
</EncryptionCard>,
|
</EncryptionCard>,
|
||||||
);
|
);
|
||||||
|
@@ -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("<ResetIdentityPanel />", () => {
|
||||||
|
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(
|
||||||
|
<ResetIdentityPanel onFinish={onFinish} onCancelClick={jest.fn()} />,
|
||||||
|
withClientContextRenderOptions(matrixClient),
|
||||||
|
);
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
|
||||||
|
await user.click(screen.getByRole("button", { name: "Continue" }));
|
||||||
|
expect(matrixClient.getCrypto()!.resetEncryption).toHaveBeenCalled();
|
||||||
|
expect(onFinish).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,253 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<AdvancedPanel /> <EncryptionDetails /> should display a spinner when loading the device keys 1`] = `
|
||||||
|
<div
|
||||||
|
class="mx_EncryptionDetails"
|
||||||
|
data-testid="encryptionDetails"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_EncryptionDetails_session"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="mx_EncryptionDetails_session_title"
|
||||||
|
>
|
||||||
|
Encryption details
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
Session ID:
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
data-testid="deviceId"
|
||||||
|
>
|
||||||
|
ABCDEFGHI
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
Session key:
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
data-testid="sessionKey"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-label="Loading…"
|
||||||
|
class="_icon_1ye7b_27"
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
style="width: 20px; height: 20px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2Z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_EncryptionDetails_buttons"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 16a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 15V7.85L9.125 9.725c-.2.2-.433.3-.7.3-.267 0-.508-.108-.725-.325a.93.93 0 0 1-.288-.712A.977.977 0 0 1 7.7 8.3l3.6-3.6c.1-.1.208-.17.325-.212.117-.042.242-.063.375-.063s.258.02.375.063a.877.877 0 0 1 .325.212l3.6 3.6c.2.2.296.438.287.713a.977.977 0 0 1-.287.687c-.2.2-.438.304-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15c0 .283-.096.52-.287.713A.968.968 0 0 1 12 16Zm-6 4c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 18v-2c0-.283.096-.52.287-.713A.968.968 0 0 1 5 15c.283 0 .52.096.713.287.191.192.287.43.287.713v2h12v-2a.97.97 0 0 1 .288-.713A.968.968 0 0 1 19 15a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v2c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 20H6Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Export keys
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 15.575c-.133 0-.258-.02-.375-.063a.877.877 0 0 1-.325-.212l-3.6-3.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7c.183-.183.42-.28.712-.288.292-.008.53.08.713.263L11 12.15V5c0-.283.096-.52.287-.713A.968.968 0 0 1 12 4c.283 0 .52.096.713.287.191.192.287.43.287.713v7.15l1.875-1.875c.183-.183.42-.27.713-.263.291.009.529.105.712.288a.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-3.6 3.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063ZM6 20c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 18v-2c0-.283.096-.52.287-.713A.967.967 0 0 1 5 15c.283 0 .52.096.713.287.191.192.287.43.287.713v2h12v-2a.97.97 0 0 1 .288-.713A.968.968 0 0 1 19 15a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v2c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 20H6Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Import keys
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="_button_i91xf_17 _destructive_i91xf_116"
|
||||||
|
data-kind="tertiary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Reset cryptographic identity
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<AdvancedPanel /> <EncryptionDetails /> should display the device keys 1`] = `
|
||||||
|
<div
|
||||||
|
class="mx_EncryptionDetails"
|
||||||
|
data-testid="encryptionDetails"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_EncryptionDetails_session"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="mx_EncryptionDetails_session_title"
|
||||||
|
>
|
||||||
|
Encryption details
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
Session ID:
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
data-testid="deviceId"
|
||||||
|
>
|
||||||
|
ABCDEFGHI
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
Session key:
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
data-testid="sessionKey"
|
||||||
|
>
|
||||||
|
ed25519
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_EncryptionDetails_buttons"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 16a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 15V7.85L9.125 9.725c-.2.2-.433.3-.7.3-.267 0-.508-.108-.725-.325a.93.93 0 0 1-.288-.712A.977.977 0 0 1 7.7 8.3l3.6-3.6c.1-.1.208-.17.325-.212.117-.042.242-.063.375-.063s.258.02.375.063a.877.877 0 0 1 .325.212l3.6 3.6c.2.2.296.438.287.713a.977.977 0 0 1-.287.687c-.2.2-.438.304-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15c0 .283-.096.52-.287.713A.968.968 0 0 1 12 16Zm-6 4c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 18v-2c0-.283.096-.52.287-.713A.968.968 0 0 1 5 15c.283 0 .52.096.713.287.191.192.287.43.287.713v2h12v-2a.97.97 0 0 1 .288-.713A.968.968 0 0 1 19 15a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v2c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 20H6Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Export keys
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 15.575c-.133 0-.258-.02-.375-.063a.877.877 0 0 1-.325-.212l-3.6-3.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7c.183-.183.42-.28.712-.288.292-.008.53.08.713.263L11 12.15V5c0-.283.096-.52.287-.713A.968.968 0 0 1 12 4c.283 0 .52.096.713.287.191.192.287.43.287.713v7.15l1.875-1.875c.183-.183.42-.27.713-.263.291.009.529.105.712.288a.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-3.6 3.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063ZM6 20c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 18v-2c0-.283.096-.52.287-.713A.967.967 0 0 1 5 15c.283 0 .52.096.713.287.191.192.287.43.287.713v2h12v-2a.97.97 0 0 1 .288-.713A.968.968 0 0 1 19 15a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v2c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 20H6Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Import keys
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="_button_i91xf_17 _destructive_i91xf_116"
|
||||||
|
data-kind="tertiary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Reset cryptographic identity
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<AdvancedPanel /> <OtherSettings /> should display the blacklist of unverified devices settings 1`] = `
|
||||||
|
<form
|
||||||
|
class="_root_ssths_24 mx_OtherSettings"
|
||||||
|
data-testid="otherSettings"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="mx_OtherSettings_title"
|
||||||
|
>
|
||||||
|
Other people’s devices
|
||||||
|
</h3>
|
||||||
|
<div
|
||||||
|
class="_inline-field_ssths_40"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_inline-field-control_ssths_52"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_container_qnvru_18"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-describedby="radix-:r7:"
|
||||||
|
checked=""
|
||||||
|
class="_input_qnvru_32"
|
||||||
|
id="radix-:r6:"
|
||||||
|
name="neverSendEncrypted"
|
||||||
|
title=""
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="_ui_qnvru_42"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="_inline-field-body_ssths_46"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="_label_ssths_67"
|
||||||
|
for="radix-:r6:"
|
||||||
|
>
|
||||||
|
Never send encrypted messages to unverified devices
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="_message_ssths_93 _help-message_ssths_99"
|
||||||
|
id="radix-:r7:"
|
||||||
|
>
|
||||||
|
By default in encrypted rooms, do not send encrypted messages to anyone until you’ve verified them
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`;
|
@@ -4,6 +4,7 @@ exports[`<RecoveryPanel /> should allow to change the recovery key when everythi
|
|||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<div
|
||||||
class="mx_SettingsSection mx_SettingsSection_newUi"
|
class="mx_SettingsSection mx_SettingsSection_newUi"
|
||||||
|
data-testid="recoveryPanel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_SettingsSection_header"
|
class="mx_SettingsSection_header"
|
||||||
@@ -44,6 +45,7 @@ exports[`<RecoveryPanel /> should ask to enter the recovery key when secrets are
|
|||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<div
|
||||||
class="mx_SettingsSection mx_SettingsSection_newUi"
|
class="mx_SettingsSection mx_SettingsSection_newUi"
|
||||||
|
data-testid="recoveryPanel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_SettingsSection_header"
|
class="mx_SettingsSection_header"
|
||||||
@@ -104,6 +106,7 @@ exports[`<RecoveryPanel /> should ask to set up a recovery key when there is no
|
|||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<div
|
||||||
class="mx_SettingsSection mx_SettingsSection_newUi"
|
class="mx_SettingsSection mx_SettingsSection_newUi"
|
||||||
|
data-testid="recoveryPanel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_SettingsSection_header"
|
class="mx_SettingsSection_header"
|
||||||
@@ -147,6 +150,7 @@ exports[`<RecoveryPanel /> should be in loading state when checking the recovery
|
|||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<div
|
||||||
class="mx_SettingsSection mx_SettingsSection_newUi"
|
class="mx_SettingsSection mx_SettingsSection_newUi"
|
||||||
|
data-testid="recoveryPanel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_SettingsSection_header"
|
class="mx_SettingsSection_header"
|
||||||
|
@@ -0,0 +1,184 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<ResetIdentityPanel /> should reset the encryption when the continue button is clicked 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<nav
|
||||||
|
class="_breadcrumb_ikpbb_17"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Back"
|
||||||
|
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
|
||||||
|
role="button"
|
||||||
|
style="--cpd-icon-button-size: 28px;"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_indicator-icon_133tf_26"
|
||||||
|
style="--cpd-icon-button-size: 100%;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<ol
|
||||||
|
class="_pages_ikpbb_26"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="_link_ue21z_17"
|
||||||
|
data-kind="primary"
|
||||||
|
data-size="small"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Encryption
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
aria-current="page"
|
||||||
|
class="_last-page_ikpbb_39"
|
||||||
|
>
|
||||||
|
Reset encryption
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<div
|
||||||
|
class="mx_EncryptionCard mx_ResetIdentityPanel"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_EncryptionCard_header"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_content_md016_17 _destructive_md016_43"
|
||||||
|
data-size="large"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Are you sure you want to reset your identity?
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_ResetIdentityPanel_content"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="_visual-list_4dcf8_17"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="_visual-list-item_bqeu7_17"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="_visual-list-item-icon_bqeu7_26 _visual-list-item-icon-success_bqeu7_31"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Your account details, contacts, preferences, and chat list will be kept
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="_visual-list-item_bqeu7_17"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="_visual-list-item-icon_bqeu7_26"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M11.287 7.287A.968.968 0 0 1 12 7c.283 0 .52.096.713.287.191.192.287.43.287.713s-.096.52-.287.713A.968.968 0 0 1 12 9a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 8c0-.283.096-.52.287-.713Zm0 4A.968.968 0 0 1 12 11c.283 0 .52.096.713.287.191.192.287.43.287.713v4a.97.97 0 0 1-.287.712A.968.968 0 0 1 12 17a.968.968 0 0 1-.713-.288A.968.968 0 0 1 11 16v-4c0-.283.096-.52.287-.713Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0Z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
You will lose any message history that’s stored only on the server
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="_visual-list-item_bqeu7_17"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="_visual-list-item-icon_bqeu7_26"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M11.287 7.287A.968.968 0 0 1 12 7c.283 0 .52.096.713.287.191.192.287.43.287.713s-.096.52-.287.713A.968.968 0 0 1 12 9a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 8c0-.283.096-.52.287-.713Zm0 4A.968.968 0 0 1 12 11c.283 0 .52.096.713.287.191.192.287.43.287.713v4a.97.97 0 0 1-.287.712A.968.968 0 0 1 12 17a.968.968 0 0 1-.713-.288A.968.968 0 0 1 11 16v-4c0-.283.096-.52.287-.713Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0Z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
You will need to verify all your existing devices and contacts again
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<span>
|
||||||
|
Only do this if you believe your account has been compromised.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_ResetIdentityPanel_footer"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="_button_i91xf_17 _destructive_i91xf_116"
|
||||||
|
data-kind="primary"
|
||||||
|
data-size="lg"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="_button_i91xf_17"
|
||||||
|
data-kind="tertiary"
|
||||||
|
data-size="lg"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
@@ -94,4 +94,19 @@ describe("<EncryptionUserSettingsTab />", () => {
|
|||||||
await waitFor(() => expect(screen.getByText("Set up recovery")).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByText("Set up recovery")).toBeInTheDocument());
|
||||||
expect(asFragment()).toMatchSnapshot();
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -81,6 +81,198 @@ exports[`<EncryptionUserSettingsTab /> should display the change recovery key pa
|
|||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`<EncryptionUserSettingsTab /> should display the reset identity panel when the user clicks on the reset cryptographic identity panel 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
class="mx_SettingsTab mx_EncryptionUserSettingsTab"
|
||||||
|
data-testid="encryptionTab"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_SettingsTab_sections"
|
||||||
|
>
|
||||||
|
<nav
|
||||||
|
class="_breadcrumb_ikpbb_17"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Back"
|
||||||
|
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
|
||||||
|
role="button"
|
||||||
|
style="--cpd-icon-button-size: 28px;"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_indicator-icon_133tf_26"
|
||||||
|
style="--cpd-icon-button-size: 100%;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<ol
|
||||||
|
class="_pages_ikpbb_26"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="_link_ue21z_17"
|
||||||
|
data-kind="primary"
|
||||||
|
data-size="small"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Encryption
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
aria-current="page"
|
||||||
|
class="_last-page_ikpbb_39"
|
||||||
|
>
|
||||||
|
Reset encryption
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<div
|
||||||
|
class="mx_EncryptionCard mx_ResetIdentityPanel"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_EncryptionCard_header"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_content_md016_17 _destructive_md016_43"
|
||||||
|
data-size="large"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Are you sure you want to reset your identity?
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_ResetIdentityPanel_content"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="_visual-list_4dcf8_17"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="_visual-list-item_bqeu7_17"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="_visual-list-item-icon_bqeu7_26 _visual-list-item-icon-success_bqeu7_31"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Your account details, contacts, preferences, and chat list will be kept
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="_visual-list-item_bqeu7_17"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="_visual-list-item-icon_bqeu7_26"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M11.287 7.287A.968.968 0 0 1 12 7c.283 0 .52.096.713.287.191.192.287.43.287.713s-.096.52-.287.713A.968.968 0 0 1 12 9a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 8c0-.283.096-.52.287-.713Zm0 4A.968.968 0 0 1 12 11c.283 0 .52.096.713.287.191.192.287.43.287.713v4a.97.97 0 0 1-.287.712A.968.968 0 0 1 12 17a.968.968 0 0 1-.713-.288A.968.968 0 0 1 11 16v-4c0-.283.096-.52.287-.713Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0Z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
You will lose any message history that’s stored only on the server
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="_visual-list-item_bqeu7_17"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="_visual-list-item-icon_bqeu7_26"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M11.287 7.287A.968.968 0 0 1 12 7c.283 0 .52.096.713.287.191.192.287.43.287.713s-.096.52-.287.713A.968.968 0 0 1 12 9a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 8c0-.283.096-.52.287-.713Zm0 4A.968.968 0 0 1 12 11c.283 0 .52.096.713.287.191.192.287.43.287.713v4a.97.97 0 0 1-.287.712A.968.968 0 0 1 12 17a.968.968 0 0 1-.713-.288A.968.968 0 0 1 11 16v-4c0-.283.096-.52.287-.713Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0Z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
You will need to verify all your existing devices and contacts again
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<span>
|
||||||
|
Only do this if you believe your account has been compromised.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_ResetIdentityPanel_footer"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="_button_i91xf_17 _destructive_i91xf_116"
|
||||||
|
data-kind="primary"
|
||||||
|
data-size="lg"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="_button_i91xf_17"
|
||||||
|
data-kind="tertiary"
|
||||||
|
data-size="lg"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`<EncryptionUserSettingsTab /> should display the set up recovery key when the user clicks on the set up recovery key button 1`] = `
|
exports[`<EncryptionUserSettingsTab /> should display the set up recovery key when the user clicks on the set up recovery key button 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<div
|
||||||
|
@@ -3467,10 +3467,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-2.1.3.tgz#8205ffb455a09d71a02d838f3dbb8503c4e6ec27"
|
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==
|
integrity sha512-U4UF7MVguENf0lQnkU2a9p/3llTsLXzbzmFFOxi0h6ny2igNxZj/kROP/jXTxxV9xD4TNn3z098Bos4J/qJpBA==
|
||||||
|
|
||||||
"@vector-im/compound-web@^7.5.0":
|
"@vector-im/compound-web@^7.6.1":
|
||||||
version "7.5.0"
|
version "7.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.5.0.tgz#1547af5f0ee27b94f79ab11eee006059f3d09707"
|
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.6.1.tgz#c41fc8b2e4c5938041e1f0ff9792f8fbadd9ab87"
|
||||||
integrity sha512-Xhef8H5WrRmPuanzRBs8rnl+hwbcQnC7nKSCupUczAQ5hjlieBx4vcQYQ/nMkrs4rMGjgfFtR3E18wT5LlML/A==
|
integrity sha512-LdHGFslkyky2aNPZwIOY9GgWn1VOUa2EBKHln8HBvpxnYPcs3/A2nb1+6SsJ7+Y0TzKc2HA0rZ3qPDhQ3hjZYQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@floating-ui/react" "^0.27.0"
|
"@floating-ui/react" "^0.27.0"
|
||||||
"@radix-ui/react-context-menu" "^2.2.1"
|
"@radix-ui/react-context-menu" "^2.2.1"
|
||||||
|