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
This commit is contained in:
Andy Balaam
2025-08-05 15:57:40 +01:00
committed by GitHub
parent 1e6f9dd096
commit c1a163cbc9
6 changed files with 31 additions and 17 deletions

View File

@@ -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();

View File

@@ -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();

View File

@@ -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: "";

View File

@@ -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<IProps, IState> {
private inputRef = React.createRef<HTMLTextAreaElement>();
private inputRef = React.createRef<HTMLInputElement>();
public constructor(props: IProps) {
super(props);
@@ -119,7 +118,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
});
}
private onRecoveryKeyChange = (ev: ChangeEvent<HTMLTextAreaElement>): void => {
private onRecoveryKeyChange = (ev: ChangeEvent<HTMLInputElement>): void => {
this.setState({
recoveryKey: ev.target.value,
});
@@ -181,17 +180,14 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
autoComplete="off"
>
<div className="mx_AccessSecretStorageDialog_recoveryKeyEntry">
<Field
inputRef={this.inputRef}
element="textarea"
rows={2}
cols={45}
<PasswordInput
ref={this.inputRef}
id="mx_securityKey"
label={_t("encryption|access_secret_storage_dialog|security_key_title")}
title={_t("encryption|access_secret_storage_dialog|security_key_label")}
placeholder={_t("encryption|access_secret_storage_dialog|security_key_label")}
value={this.state.recoveryKey}
onChange={this.onRecoveryKeyChange}
autoFocus={true}
forceValidity={this.state.recoveryKeyCorrect ?? undefined}
autoComplete="off"
/>
</div>

View File

@@ -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",

View File

@@ -31,7 +31,7 @@ describe("AccessSecretStorageDialog", () => {
const enterRecoveryKey = async (valueToEnter: string = recoveryKey): Promise<void> => {
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");