diff --git a/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts b/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts index 6bffd793f60..a2799618143 100644 --- a/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts +++ b/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts @@ -93,4 +93,22 @@ test.describe("Encryption tab", () => { await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true); }, ); + + test("should display the reset identity panel when the user clicks on 'Forgot recovery key?'", async ({ + page, + app, + util, + }) => { + await verifySession(app, "new passphrase"); + // We need to delete the cached secrets + await deleteCachedSecrets(page); + + // The "Key storage is out sync" section is displayed and the user click on the "Forgot recovery key?" button + await util.openEncryptionTab(); + const dialog = util.getEncryptionTabContent(); + await dialog.getByRole("button", { name: "Forgot recovery key?" }).click(); + + // The user is prompted to reset their identity + await expect(dialog.getByText("Forgot your recovery key? You’ll need to reset your identity.")).toBeVisible(); + }); }); diff --git a/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png index e6664a5f79b..f1a9152e89a 100644 Binary files a/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png and b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png differ diff --git a/res/css/_components.pcss b/res/css/_components.pcss index a114c998b8b..46b8fc932d9 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -358,6 +358,7 @@ @import "./views/settings/encryption/_AdvancedPanel.pcss"; @import "./views/settings/encryption/_ChangeRecoveryKey.pcss"; @import "./views/settings/encryption/_EncryptionCard.pcss"; +@import "./views/settings/encryption/_RecoveryPanelOutOfSync.pcss"; @import "./views/settings/encryption/_ResetIdentityPanel.pcss"; @import "./views/settings/tabs/_SettingsBanner.pcss"; @import "./views/settings/tabs/_SettingsIndent.pcss"; diff --git a/res/css/views/settings/encryption/_RecoveryPanelOutOfSync.pcss b/res/css/views/settings/encryption/_RecoveryPanelOutOfSync.pcss new file mode 100644 index 00000000000..fc6ba7d9593 --- /dev/null +++ b/res/css/views/settings/encryption/_RecoveryPanelOutOfSync.pcss @@ -0,0 +1,11 @@ +/* + * 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. + */ + +.mx_RecoveryPanelOutOfSync { + display: flex; + gap: var(--cpd-space-2x); +} diff --git a/src/components/views/settings/encryption/RecoveryPanelOutOfSync.tsx b/src/components/views/settings/encryption/RecoveryPanelOutOfSync.tsx index 7b5bea56234..a5d47100d6f 100644 --- a/src/components/views/settings/encryption/RecoveryPanelOutOfSync.tsx +++ b/src/components/views/settings/encryption/RecoveryPanelOutOfSync.tsx @@ -19,6 +19,10 @@ interface RecoveryPanelOutOfSyncProps { * Callback for when the user has finished entering their recovery key. */ onFinish: () => void; + /** + * Callback for when the user clicks on the "Forgot recovery key?" button. + */ + onForgotRecoveryKey: () => void; } /** @@ -28,7 +32,7 @@ interface RecoveryPanelOutOfSyncProps { * It prompts the user to enter their recovery key so that the secrets can be loaded from 4S into * the client. */ -export function RecoveryPanelOutOfSync({ onFinish }: RecoveryPanelOutOfSyncProps): JSX.Element { +export function RecoveryPanelOutOfSync({ onForgotRecoveryKey, onFinish }: RecoveryPanelOutOfSyncProps): JSX.Element { return ( - +
+ + +
); } diff --git a/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx b/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx index f164342e27d..0b4b27e9d07 100644 --- a/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx @@ -71,7 +71,12 @@ export function EncryptionUserSettingsTab({ initialState = "loading" }: Encrypti content = ; break; case "secrets_not_cached": - content = ; + content = ( + setState("reset_identity_forgot")} + /> + ); break; case "main": content = ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3896d05e885..c9adce2cc76 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2496,7 +2496,8 @@ "description": "Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.", "enter_key_error": "The recovery key you entered is not correct.", "enter_recovery_key": "Enter recovery key", - "key_storage_warning": "Your key storage is out of sync. Click the button below to fix the problem.", + "forgot_recovery_key": "Forgot recovery key?", + "key_storage_warning": "Your key storage is out of sync. Click one of the buttons below to fix the problem.", "save_key_description": "Do not share this with anyone!", "save_key_title": "Recovery key", "set_up_recovery": "Set up recovery", diff --git a/test/unit-tests/components/views/settings/encryption/RecoveryPanelOutOfSync-test.tsx b/test/unit-tests/components/views/settings/encryption/RecoveryPanelOutOfSync-test.tsx new file mode 100644 index 00000000000..36e35dbe832 --- /dev/null +++ b/test/unit-tests/components/views/settings/encryption/RecoveryPanelOutOfSync-test.tsx @@ -0,0 +1,51 @@ +/* + * 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 { render, screen } from "jest-matrix-react"; +import userEvent from "@testing-library/user-event"; +import { mocked } from "jest-mock"; + +import { RecoveryPanelOutOfSync } from "../../../../../../src/components/views/settings/encryption/RecoveryPanelOutOfSync"; +import { accessSecretStorage } from "../../../../../../src/SecurityManager"; + +jest.mock("../../../../../../src/SecurityManager", () => ({ + accessSecretStorage: jest.fn(), +})); + +describe("", () => { + function renderComponent(onFinish = jest.fn(), onForgotRecoveryKey = jest.fn()) { + return render(); + } + + it("should render", () => { + const { asFragment } = renderComponent(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should call onForgotRecoveryKey when the 'Forgot recovery key?' is clicked", async () => { + const user = userEvent.setup(); + + const onForgotRecoveryKey = jest.fn(); + renderComponent(jest.fn(), onForgotRecoveryKey); + + await user.click(screen.getByRole("button", { name: "Forgot recovery key?" })); + expect(onForgotRecoveryKey).toHaveBeenCalled(); + }); + + it("should access to 4S and call onFinish when 'Enter recovery key' is clicked", async () => { + const user = userEvent.setup(); + mocked(accessSecretStorage).mockClear().mockResolvedValue(); + + const onFinish = jest.fn(); + renderComponent(onFinish); + + await user.click(screen.getByRole("button", { name: "Enter recovery key" })); + expect(accessSecretStorage).toHaveBeenCalled(); + expect(onFinish).toHaveBeenCalled(); + }); +}); diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanelOutOfSync-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanelOutOfSync-test.tsx.snap new file mode 100644 index 00000000000..16cac376fa7 --- /dev/null +++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanelOutOfSync-test.tsx.snap @@ -0,0 +1,75 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render 1`] = ` + +
+
+

+ Recovery +

+
+ Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices. + + + + + Your key storage is out of sync. Click one of the buttons below to fix the problem. + +
+
+
+ + +
+
+
+`; 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 fdf8f7106dc..9137140c79c 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 @@ -10,7 +10,6 @@ import { render, screen } from "jest-matrix-react"; import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { waitFor } from "@testing-library/dom"; import userEvent from "@testing-library/user-event"; -import { mocked } from "jest-mock"; import { EncryptionUserSettingsTab, @@ -18,11 +17,6 @@ import { } from "../../../../../../../src/components/views/settings/tabs/user/EncryptionUserSettingsTab"; import { createTestClient, withClientContextRenderOptions } from "../../../../../../test-utils"; import Modal from "../../../../../../../src/Modal"; -import { accessSecretStorage } from "../../../../../../../src/SecurityManager"; - -jest.mock("../../../../../../../src/SecurityManager", () => ({ - accessSecretStorage: jest.fn(), -})); describe("", () => { let matrixClient: MatrixClient; @@ -42,8 +36,6 @@ describe("", () => { userSigningKey: true, }, }); - - mocked(accessSecretStorage).mockClear().mockResolvedValue(); }); function renderComponent(props: { initialState?: State } = {}) { @@ -79,7 +71,7 @@ describe("", () => { await waitFor(() => expect(screen.getByText("Recovery")).toBeInTheDocument()); }); - it("should ask to enter the recovery key when secrets are not cached", async () => { + it("should display the recovery out of sync panel when secrets are not cached", async () => { // Secrets are not cached jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockResolvedValue({ privateKeysInSecretStorage: true, @@ -97,8 +89,10 @@ describe("", () => { await waitFor(() => screen.getByRole("button", { name: "Enter recovery key" })); expect(asFragment()).toMatchSnapshot(); - await user.click(screen.getByRole("button", { name: "Enter recovery key" })); - expect(accessSecretStorage).toHaveBeenCalled(); + await user.click(screen.getByRole("button", { name: "Forgot recovery key?" })); + expect( + screen.getByRole("heading", { name: "Forgot your recovery key? You’ll need to reset your identity." }), + ).toBeVisible(); }); it("should display the change recovery key panel when the user clicks on the change recovery button", async () => { 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 5856e6fda33..2e507dd67af 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 @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should ask to enter the recovery key when secrets are not cached 1`] = ` +exports[` should display a verify button when the encryption is not set up 1`] = `
should ask to enter the recovery key when >
should ask to enter the recovery key when

- Recovery + Device not verified

- Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices. @@ -39,7 +37,7 @@ exports[` should ask to enter the recovery key when 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" /> - Your key storage is out of sync. Click the button below to fix the problem. + You need to verify this device in order to view your encryption settings.
@@ -59,10 +57,10 @@ exports[` should ask to enter the recovery key when xmlns="http://www.w3.org/2000/svg" > - Enter recovery key + Verify this device
@@ -70,7 +68,20 @@ exports[` should ask to enter the recovery key when
`; -exports[` should display a verify button when the encryption is not set up 1`] = ` +exports[` should display the change recovery key panel when the user clicks on the change recovery button 1`] = ` + +
+
+
+ +`; + +exports[` should display the recovery out of sync panel when secrets are not cached 1`] = `
should display a verify button when the e >
should display a verify button when the e

- Device not verified + Recovery

+ Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices. @@ -107,50 +120,50 @@ exports[` should display a verify button when the e 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" /> - You need to verify this device in order to view your encryption settings. + Your key storage is out of sync. Click one of the buttons below to fix the problem.
- + Forgot recovery key? + + +
`; -exports[` should display the change recovery key panel when the user clicks on the change recovery button 1`] = ` - -
-
-
- -`; - exports[` should display the reset identity panel when the user clicks on the reset cryptographic identity panel 1`] = `