From c1a163cbc9dd68c85505058dd47a6d1ef700ebc1 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 5 Aug 2025 15:57:40 +0100 Subject: [PATCH] Hide recovery key when prompting for verification (#30471) * Separate security_key_title from security_key_label since they differ in designs See https://www.figma.com/design/ZodBLtGnKmRTGJo5SGLnH3/ER-137--Excluding-Insecure-Devices?node-id=92-8818&t=02JILBe2n7sx7ljU-1 In parallel with this, I have updated security_key_title in localazy. * Hide recovery key on entry screen after login --- .../e2e/crypto/device-verification.spec.ts | 2 +- playwright/e2e/crypto/utils.ts | 4 ++-- .../security/_AccessSecretStorageDialog.pcss | 17 +++++++++++++++++ .../security/AccessSecretStorageDialog.tsx | 18 +++++++----------- src/i18n/strings/en_EN.json | 1 + .../dialogs/AccessSecretStorageDialog-test.tsx | 6 +++--- 6 files changed, 31 insertions(+), 17 deletions(-) diff --git a/playwright/e2e/crypto/device-verification.spec.ts b/playwright/e2e/crypto/device-verification.spec.ts index eb21dfc909..ab36c37a76 100644 --- a/playwright/e2e/crypto/device-verification.spec.ts +++ b/playwright/e2e/crypto/device-verification.spec.ts @@ -209,7 +209,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { const dialog = page.locator(".mx_Dialog"); // We use `pressSequentially` here to make sure that the FocusLock isn't causing us any problems // (cf https://github.com/element-hq/element-web/issues/30089) - await dialog.locator("textarea").pressSequentially(recoveryKey); + await dialog.getByTitle("Recovery key").pressSequentially(recoveryKey); await dialog.getByRole("button", { name: "Continue", disabled: false }).click(); await page.getByRole("button", { name: "Done" }).click(); diff --git a/playwright/e2e/crypto/utils.ts b/playwright/e2e/crypto/utils.ts index 289b123e86..0521df236e 100644 --- a/playwright/e2e/crypto/utils.ts +++ b/playwright/e2e/crypto/utils.ts @@ -228,7 +228,7 @@ export async function logIntoElement(page: Page, credentials: Credentials, secur await useSecurityKey.click(); } // Fill in the recovery key - await page.locator(".mx_Dialog").locator("textarea").fill(securityKey); + await page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey); await page.getByRole("button", { name: "Continue", disabled: false }).click(); await page.getByRole("button", { name: "Done" }).click(); } @@ -263,7 +263,7 @@ export async function verifySession(app: ElementAppPage, securityKey: string) { const settings = await app.settings.openUserSettings("Encryption"); await settings.getByRole("button", { name: "Verify this device" }).click(); await app.page.getByRole("button", { name: "Verify with Recovery Key" }).click(); - await app.page.locator(".mx_Dialog").locator("textarea").fill(securityKey); + await app.page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey); await app.page.getByRole("button", { name: "Continue", disabled: false }).click(); await app.page.getByRole("button", { name: "Done" }).click(); await app.settings.closeDialog(); diff --git a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss index 9d81097d60..2c78a62f8d 100644 --- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss +++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss @@ -15,6 +15,23 @@ Please see LICENSE files in the repository root for full details. } .mx_AccessSecretStorageDialog_primaryContainer { + .mx_AccessSecretStorageDialog_recoveryKeyEntry { + /* + * Be specific here to avoid "margin: 9px" from _common.pcss + */ + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) { + input { + /* + * From figma: https://www.figma.com/design/ZodBLtGnKmRTGJo5SGLnH3/ER-137--Excluding-Insecure-Devices?node-id=102-43729&t=QmewENUd7f6Tmw9U-1 + */ + width: 448px; + height: 70px; + margin: 0px; + border: 1px solid; + } + } + } + .mx_AccessSecretStorageDialog_recoveryKeyFeedback { &::before { content: ""; diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx index bfe99b80c8..da9a67a644 100644 --- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx +++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx @@ -6,14 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { Button } from "@vector-im/compound-web"; +import { Button, PasswordInput } from "@vector-im/compound-web"; import LockSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid"; import { debounce } from "lodash"; import classNames from "classnames"; import React, { type ChangeEvent, type FormEvent } from "react"; import { type SecretStorage } from "matrix-js-sdk/src/matrix"; -import Field from "../../elements/Field"; import { Flex } from "../../../../shared-components/utils/Flex"; import { _t } from "../../../../languageHandler"; import { EncryptionCard } from "../../settings/encryption/EncryptionCard"; @@ -53,7 +52,7 @@ interface IState { * Access Secure Secret Storage by requesting the user's passphrase. */ export default class AccessSecretStorageDialog extends React.PureComponent { - private inputRef = React.createRef(); + private inputRef = React.createRef(); public constructor(props: IProps) { super(props); @@ -119,7 +118,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent): void => { + private onRecoveryKeyChange = (ev: ChangeEvent): void => { this.setState({ recoveryKey: ev.target.value, }); @@ -181,17 +180,14 @@ export default class AccessSecretStorageDialog extends React.PureComponent
-
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b5a7c16e6e..712214e036 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -920,6 +920,7 @@ }, "privacy_warning": "Make sure nobody can see this screen!", "restoring": "Restoring keys from backup", + "security_key_label": "Recovery key", "security_key_title": "Recovery key" }, "bootstrap_title": "Setting up keys", diff --git a/test/unit-tests/components/views/dialogs/AccessSecretStorageDialog-test.tsx b/test/unit-tests/components/views/dialogs/AccessSecretStorageDialog-test.tsx index 23fa06720c..2f136cb0e8 100644 --- a/test/unit-tests/components/views/dialogs/AccessSecretStorageDialog-test.tsx +++ b/test/unit-tests/components/views/dialogs/AccessSecretStorageDialog-test.tsx @@ -31,7 +31,7 @@ describe("AccessSecretStorageDialog", () => { const enterRecoveryKey = async (valueToEnter: string = recoveryKey): Promise => { await act(async () => { - fireEvent.change(screen.getByRole("textbox"), { + fireEvent.change(screen.getByTitle("Recovery key"), { target: { value: valueToEnter, }, @@ -67,7 +67,7 @@ describe("AccessSecretStorageDialog", () => { renderComponent({ onFinished, checkPrivateKey }); // check that the input field is focused - expect(screen.getByRole("textbox")).toHaveFocus(); + expect(screen.getByTitle("Recovery key")).toHaveFocus(); await enterRecoveryKey(); await submitDialog(); @@ -111,7 +111,7 @@ describe("AccessSecretStorageDialog", () => { renderComponent({ checkPrivateKey, keyInfo }); await enterRecoveryKey(); - expect(screen.getByRole("textbox")).toHaveValue(recoveryKey); + expect(screen.getByTitle("Recovery key")).toHaveValue(recoveryKey); await expect(screen.findByText("The recovery key you entered is not correct.")).resolves.toBeInTheDocument(); expect(screen.getByText("Continue")).toHaveAttribute("aria-disabled", "true");