Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Recovery section in the new user settings Encryption tab #28673

Merged
merged 44 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3e77b3d
Refine `SettingsSection` & `SettingsTab`
florianduros Dec 4, 2024
ae623f8
Add encryption tab
florianduros Dec 4, 2024
f9e48b4
Add recovery section
florianduros Dec 6, 2024
0057f57
Add device verification
florianduros Dec 12, 2024
bb507b0
Rename `Panel` into `State`
florianduros Dec 13, 2024
1aace3f
Update & add tests to user settings common
florianduros Dec 13, 2024
70c084e
Add tests to `RecoveryPanel`
florianduros Dec 18, 2024
7193998
Add tests to `ChangeRecoveryKey`
florianduros Dec 18, 2024
fec324e
Update CreateSecretStorageDialog-test snapshot
florianduros Dec 16, 2024
44c6bce
Add tests to `EncryptionUserSettingsTab`
florianduros Dec 16, 2024
075f6dc
Update existing screenshots of e2e tests
florianduros Dec 16, 2024
895ad88
Add new encryption tab ownership to `@element-hq/element-crypto-web-r…
florianduros Dec 17, 2024
ba032a7
Add e2e tests
florianduros Dec 18, 2024
7909ac9
Fix monospace font and add figma link to hardcoded value
florianduros Dec 19, 2024
618557c
Add unit to Icon
florianduros Dec 19, 2024
c805cd8
Merge branch 'develop' into florianduros/encryption-tab
florianduros Dec 19, 2024
7a372f7
Merge branch 'develop' into florianduros/encryption-tab
florianduros Dec 23, 2024
b20579d
Improve e2e doc
florianduros Dec 23, 2024
24c537c
Assert that the crypto module is defined
florianduros Jan 6, 2025
72adfa5
Add classname doc
florianduros Jan 6, 2025
36c7e0e
Fix typo
florianduros Jan 6, 2025
52076f1
Use `good` state instead of default
florianduros Jan 6, 2025
a0d904e
Rename `ChangeRecoveryKey.isSetupFlow` into `ChangeRecoveryKey.userHa…
florianduros Jan 6, 2025
1a0e6dc
Move `deleteCachedSecrets` fixture in `recovery.spec.ts`
florianduros Jan 6, 2025
0b254e5
Use one callback instead of two in `RecoveryPanel`
florianduros Jan 6, 2025
6f236bd
Fix docs and naming of `utils.createBot`
florianduros Jan 8, 2025
82bf2cc
Fix typo in `RecoveryPanel`
florianduros Jan 8, 2025
84d11f8
Add more doc to the state of the `EncryptionUserSettingsTab`
florianduros Jan 8, 2025
2fe5555
Rename `verification_required` into `set_up_encryption`
florianduros Jan 8, 2025
4b365ba
Merge branch 'develop' into florianduros/encryption-tab
florianduros Jan 8, 2025
8a9291a
Update test
florianduros Jan 8, 2025
1c00502
ADd new license
florianduros Jan 8, 2025
dc940f5
Update comments and doc
florianduros Jan 9, 2025
521cebf
Assert that `recoveryKey.encodedPrivateKey` is always defined
florianduros Jan 9, 2025
7af44cc
Add comments to explain how the secrets could be uncached
florianduros Jan 9, 2025
8bd5d6a
Use `matrixClient.secretStorage.getDefaultKeyId` instead of `matrixCl…
florianduros Jan 9, 2025
086f28e
Merge branch 'develop' into florianduros/encryption-tab
florianduros Jan 9, 2025
0c18708
Update existing screenshot to add encryption tab.
florianduros Jan 9, 2025
0a52b7c
Update tests
florianduros Jan 9, 2025
e5dea48
Use new labels when changing the recovery key
florianduros Jan 10, 2025
f78c27a
Fix docs
florianduros Jan 13, 2025
bef3165
Don't reset key backup when creating a recovery key
florianduros Jan 13, 2025
ae163d1
Fix doc
florianduros Jan 15, 2025
ee4f3a8
Merge branch 'develop' into florianduros/encryption-tab
florianduros Jan 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions playwright/e2e/settings/encryption-user-tab/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,19 @@ class Helpers {
/**
* Get the security key from the clipboard and fill in the input field
* Then click on the finish button
* @param title - The title of the dialog
* @param confirmButtonLabel - The label of the confirm button
* @param screenshot
*/
async confirmRecoveryKey(screenshot: `${string}.png`) {
async confirmRecoveryKey(title: string, confirmButtonLabel: string, screenshot: `${string}.png`) {
const dialog = this.getEncryptionTabContent();
await expect(dialog.getByText("Enter your recovery key to confirm")).toBeVisible();
await expect(dialog.getByText(title, { exact: true })).toBeVisible();
await expect(dialog).toMatchScreenshot(screenshot);

const handle = await this.page.evaluateHandle(() => navigator.clipboard.readText());
const clipboardContent = await handle.jsonValue();
await dialog.getByRole("textbox").fill(clipboardContent);
await dialog.getByRole("button", { name: "Finish set up" }).click();
await dialog.getByRole("button", { name: confirmButtonLabel }).click();
await expect(dialog).toMatchScreenshot("default-recovery.png");
}
}
12 changes: 10 additions & 2 deletions playwright/e2e/settings/encryption-user-tab/recovery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ test.describe("Recovery section in Encryption tab", () => {
await dialog.getByRole("button", { name: "Continue" }).click();

// Confirm the recovery key
await util.confirmRecoveryKey("change-key-2-encryption-tab.png");
await util.confirmRecoveryKey(
"Enter your new recovery key",
"Confirm new recovery key",
"change-key-2-encryption-tab.png",
);
},
);

Expand Down Expand Up @@ -102,7 +106,11 @@ test.describe("Recovery section in Encryption tab", () => {
await dialog.getByRole("button", { name: "Continue" }).click();

// Confirm the recovery key
await util.confirmRecoveryKey("set-up-key-3-encryption-tab.png");
await util.confirmRecoveryKey(
"Enter your recovery key to confirm",
"Finish set up",
"set-up-key-3-encryption-tab.png",
);

// The recovery key is now set up and the user can change it
await expect(dialog.getByRole("button", { name: "Change recovery key" })).toBeVisible();
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 36 additions & 9 deletions src/components/views/settings/encryption/ChangeRecoveryKey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,15 @@ import { withSecretStorageKeyCache } from "../../../../SecurityManager";
* - `inform_user`: The user is informed about the recovery key.
* - `save_key_setup_flow`: The user is asked to save the new recovery key during the setup flow.
* - `save_key_change_flow`: The user is asked to save the new recovery key during the change key flow.
* - `confirm`: The user is asked to confirm the new recovery key.
* - `confirm_key_setup_flow`: The user is asked to confirm the new recovery key during the set up flow.
* - `confirm_key_change_flow`: The user is asked to confirm the new recovery key during the change key flow.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to say, I wonder if it wouldn't be simpler to just have three states (inform_user, save_key, confirm_key) and distinguish between the two flows by looking at the userHasRecoveryKey prop.

On the other hand, we should probably just get on and land this rather than messing with it forever. A thought for the future though.

*/
type State = "inform_user" | "save_key_setup_flow" | "save_key_change_flow" | "confirm";
type State =
| "inform_user"
| "save_key_setup_flow"
| "save_key_change_flow"
| "confirm_key_setup_flow"
| "confirm_key_change_flow";

interface ChangeRecoveryKeyProps {
/**
Expand Down Expand Up @@ -89,12 +95,19 @@ export function ChangeRecoveryKey({
<KeyPanel
// encodedPrivateKey is always defined, the optional typing is incorrect
recoveryKey={recoveryKey.encodedPrivateKey!}
onConfirmClick={() => setState("confirm")}
onConfirmClick={() =>
setState((currentState) =>
currentState === "save_key_change_flow"
? "confirm_key_change_flow"
: "confirm_key_setup_flow",
)
}
onCancelClick={onCancelClick}
/>
);
break;
case "confirm":
case "confirm_key_setup_flow":
case "confirm_key_change_flow":
// Ask the user to enter the recovery key they just save to confirm it.
florianduros marked this conversation as resolved.
Show resolved Hide resolved
content = (
<KeyForm
Expand All @@ -120,6 +133,11 @@ export function ChangeRecoveryKey({
logger.error("Failed to bootstrap secret storage", e);
}
}}
submitButtonLabel={
state === "confirm_key_setup_flow"
? _t("settings|encryption|recovery|set_up_recovery_confirm_button")
: _t("settings|encryption|recovery|change_recovery_confirm_button")
}
/>
);
}
Expand Down Expand Up @@ -181,10 +199,15 @@ function getLabels(state: State): Labels {
title: _t("settings|encryption|recovery|change_recovery_key_title"),
description: _t("settings|encryption|recovery|change_recovery_key_description"),
};
case "confirm":
case "confirm_key_setup_flow":
return {
title: _t("settings|encryption|recovery|confirm_title"),
description: _t("settings|encryption|recovery|confirm_description"),
title: _t("settings|encryption|recovery|set_up_recovery_confirm_title"),
description: _t("settings|encryption|recovery|set_up_recovery_confirm_description"),
};
case "confirm_key_change_flow":
return {
title: _t("settings|encryption|recovery|change_recovery_confirm_title"),
description: _t("settings|encryption|recovery|change_recovery_confirm_description"),
};
}
}
Expand Down Expand Up @@ -279,14 +302,18 @@ interface KeyFormProps {
* The recovery key to confirm.
*/
recoveryKey: string;
/**
* The label for the submit button.
*/
submitButtonLabel: string;
}

/**
* The form to confirm the recovery key.
* The finish button is disabled until the key is filled and valid.
* The entered key is valid if it matches the recovery key.
*/
function KeyForm({ onCancelClick, onSubmit, recoveryKey }: KeyFormProps): JSX.Element {
function KeyForm({ onCancelClick, onSubmit, recoveryKey, submitButtonLabel }: KeyFormProps): JSX.Element {
// Undefined by default, as the key is not filled yet
const [isKeyValid, setIsKeyValid] = useState<boolean>();
const isKeyInvalidAndFilled = isKeyValid === false;
Expand Down Expand Up @@ -316,7 +343,7 @@ function KeyForm({ onCancelClick, onSubmit, recoveryKey }: KeyFormProps): JSX.El
)}
</Field>
<div className="mx_ChangeRecoveryKey_footer">
<Button disabled={!isKeyValid}>{_t("settings|encryption|recovery|confirm_finish")}</Button>
<Button disabled={!isKeyValid}>{submitButtonLabel}</Button>
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
Expand Down
11 changes: 7 additions & 4 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2472,19 +2472,22 @@
"device_not_verified_title": "Device not verified",
"dialog_title": "<strong>Settings:</strong> Encryption",
"recovery": {
"change_recovery_confirm_button": "Confirm new recovery key",
"change_recovery_confirm_description": "Enter your new recovery key below to finish. Your old one will no longer work.",
"change_recovery_confirm_title": "Enter your new recovery key",
"change_recovery_key": "Change recovery key",
"change_recovery_key_description": "Get a new recovery key if you've lost your existing one. After changing your recovery key, your old one will no longer work.",
"change_recovery_key_description": "Write down this new recovery key somewhere safe. Then click Continue to confirm the change.",
"change_recovery_key_title": "Change recovery key?",
"confirm_description": "Enter the recovery key shown on the previous screen to finish setting up recovery.",
"confirm_finish": "Finish set up",
"confirm_title": "Enter your recovery key to confirm",
"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.",
"save_key_description": "Do not share this with anyone!",
"save_key_title": "Recovery key",
"set_up_recovery": "Set up recovery",
"set_up_recovery_confirm_button": "Finish set up",
"set_up_recovery_confirm_description": "Enter the recovery key shown on the previous screen to finish setting up recovery.",
"set_up_recovery_confirm_title": "Enter your recovery key to confirm",
"set_up_recovery_description": "Your key storage is protected by a recovery key. If you need a new recovery key after setup, you can recreate it by selecting ‘%(changeRecoveryKeyButton)s’.",
"set_up_recovery_save_key_description": "Write down this recovery key somewhere safe, like a password manager, encrypted note, or a physical safe.",
"set_up_recovery_save_key_title": "Save your recovery key somewhere safe",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ exports[`<ChangeRecoveryKey /> flow to change the recovery key should display th
Change recovery key?
</h2>
<span>
Get a new recovery key if you've lost your existing one. After changing your recovery key, your old one will no longer work.
Write down this new recovery key somewhere safe. Then click Continue to confirm the change.
</span>
</div>
<div
Expand Down
Loading