-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Add Recovery
section in the new user settings Encryption
tab
#28673
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
Merged
Merged
Changes from all commits
Commits
Show all changes
44 commits
Select commit
Hold shift + click to select a range
3e77b3d
Refine `SettingsSection` & `SettingsTab`
florianduros ae623f8
Add encryption tab
florianduros f9e48b4
Add recovery section
florianduros 0057f57
Add device verification
florianduros bb507b0
Rename `Panel` into `State`
florianduros 1aace3f
Update & add tests to user settings common
florianduros 70c084e
Add tests to `RecoveryPanel`
florianduros 7193998
Add tests to `ChangeRecoveryKey`
florianduros fec324e
Update CreateSecretStorageDialog-test snapshot
florianduros 44c6bce
Add tests to `EncryptionUserSettingsTab`
florianduros 075f6dc
Update existing screenshots of e2e tests
florianduros 895ad88
Add new encryption tab ownership to `@element-hq/element-crypto-web-r…
florianduros ba032a7
Add e2e tests
florianduros 7909ac9
Fix monospace font and add figma link to hardcoded value
florianduros 618557c
Add unit to Icon
florianduros c805cd8
Merge branch 'develop' into florianduros/encryption-tab
florianduros 7a372f7
Merge branch 'develop' into florianduros/encryption-tab
florianduros b20579d
Improve e2e doc
florianduros 24c537c
Assert that the crypto module is defined
florianduros 72adfa5
Add classname doc
florianduros 36c7e0e
Fix typo
florianduros 52076f1
Use `good` state instead of default
florianduros a0d904e
Rename `ChangeRecoveryKey.isSetupFlow` into `ChangeRecoveryKey.userHa…
florianduros 1a0e6dc
Move `deleteCachedSecrets` fixture in `recovery.spec.ts`
florianduros 0b254e5
Use one callback instead of two in `RecoveryPanel`
florianduros 6f236bd
Fix docs and naming of `utils.createBot`
florianduros 82bf2cc
Fix typo in `RecoveryPanel`
florianduros 84d11f8
Add more doc to the state of the `EncryptionUserSettingsTab`
florianduros 2fe5555
Rename `verification_required` into `set_up_encryption`
florianduros 4b365ba
Merge branch 'develop' into florianduros/encryption-tab
florianduros 8a9291a
Update test
florianduros 1c00502
ADd new license
florianduros dc940f5
Update comments and doc
florianduros 521cebf
Assert that `recoveryKey.encodedPrivateKey` is always defined
florianduros 7af44cc
Add comments to explain how the secrets could be uncached
florianduros 8bd5d6a
Use `matrixClient.secretStorage.getDefaultKeyId` instead of `matrixCl…
florianduros 086f28e
Merge branch 'develop' into florianduros/encryption-tab
florianduros 0c18708
Update existing screenshot to add encryption tab.
florianduros 0a52b7c
Update tests
florianduros e5dea48
Use new labels when changing the recovery key
florianduros f78c27a
Fix docs
florianduros bef3165
Don't reset key backup when creating a recovery key
florianduros ae163d1
Fix doc
florianduros ee4f3a8
Merge branch 'develop' into florianduros/encryption-tab
florianduros File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* 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 { Page } from "@playwright/test"; | ||
import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api"; | ||
|
||
import { ElementAppPage } from "../../../pages/ElementAppPage"; | ||
import { test as base, expect } from "../../../element-web-test"; | ||
export { expect }; | ||
|
||
/** | ||
* Set up for the encryption tab test | ||
*/ | ||
export const test = base.extend<{ | ||
util: Helpers; | ||
}>({ | ||
util: async ({ page, app, bot }, use) => { | ||
await use(new Helpers(page, app)); | ||
}, | ||
}); | ||
|
||
class Helpers { | ||
constructor( | ||
private page: Page, | ||
private app: ElementAppPage, | ||
) {} | ||
|
||
/** | ||
* Open the encryption tab | ||
*/ | ||
openEncryptionTab() { | ||
return this.app.settings.openUserSettings("Encryption"); | ||
} | ||
|
||
/** | ||
* Go through the device verification flow using the recovery key. | ||
*/ | ||
async verifyDevice(recoveryKey: GeneratedSecretStorageKey) { | ||
// Select the security phrase | ||
await this.page.getByRole("button", { name: "Verify with Security Key or Phrase" }).click(); | ||
await this.enterRecoveryKey(recoveryKey); | ||
await this.page.getByRole("button", { name: "Done" }).click(); | ||
} | ||
|
||
/** | ||
* Fill the recovery key in the dialog | ||
* @param recoveryKey | ||
*/ | ||
async enterRecoveryKey(recoveryKey: GeneratedSecretStorageKey) { | ||
// Select to use recovery key | ||
await this.page.getByRole("button", { name: "use your Security Key" }).click(); | ||
|
||
// Fill the recovery key | ||
const dialog = this.page.locator(".mx_Dialog"); | ||
await dialog.getByRole("textbox").fill(recoveryKey.encodedPrivateKey); | ||
await dialog.getByRole("button", { name: "Continue" }).click(); | ||
} | ||
|
||
/** | ||
* Get the encryption tab content | ||
*/ | ||
getEncryptionTabContent() { | ||
return this.page.getByTestId("encryptionTab"); | ||
} | ||
|
||
/** | ||
* Set the default key id of the secret storage to `null` | ||
*/ | ||
async removeSecretStorageDefaultKeyId() { | ||
const client = await this.app.client.prepareClient(); | ||
await client.evaluate(async (client) => { | ||
await client.secretStorage.setDefaultKeyId(null); | ||
}); | ||
} | ||
|
||
/** | ||
* 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(title: string, confirmButtonLabel: string, screenshot: `${string}.png`) { | ||
const dialog = this.getEncryptionTabContent(); | ||
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: confirmButtonLabel }).click(); | ||
await expect(dialog).toMatchScreenshot("default-recovery.png"); | ||
} | ||
} |
178 changes: 178 additions & 0 deletions
178
playwright/e2e/settings/encryption-user-tab/recovery.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
/* | ||
* 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 { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api"; | ||
import { Page } from "@playwright/test"; | ||
|
||
import { test, expect } from "."; | ||
import { | ||
checkDeviceIsConnectedKeyBackup, | ||
checkDeviceIsCrossSigned, | ||
createBot, | ||
verifySession, | ||
} from "../../crypto/utils"; | ||
|
||
test.describe("Recovery section in Encryption tab", () => { | ||
test.use({ | ||
displayName: "Alice", | ||
}); | ||
|
||
let recoveryKey: GeneratedSecretStorageKey; | ||
let expectedBackupVersion: string; | ||
|
||
test.beforeEach(async ({ page, homeserver, credentials }) => { | ||
const res = await createBot(page, homeserver, credentials); | ||
recoveryKey = res.recoveryKey; | ||
expectedBackupVersion = res.expectedBackupVersion; | ||
}); | ||
|
||
test("should verify the device", { tag: "@screenshot" }, async ({ page, app, util }) => { | ||
const dialog = await util.openEncryptionTab(); | ||
|
||
// 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" }); | ||
await expect(verifyButton).toBeVisible(); | ||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("verify-device-encryption-tab.png"); | ||
await verifyButton.click(); | ||
|
||
await util.verifyDevice(recoveryKey); | ||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("default-recovery.png"); | ||
|
||
// Check that our device is now cross-signed | ||
await checkDeviceIsCrossSigned(app); | ||
|
||
// Check that the current device is connected to key backup | ||
// The backup decryption key should be in cache also, as we got it directly from the 4S | ||
await app.closeDialog(); | ||
await checkDeviceIsConnectedKeyBackup(page, expectedBackupVersion, true); | ||
}); | ||
|
||
test( | ||
"should change the recovery key", | ||
{ tag: "@screenshot" }, | ||
async ({ page, app, homeserver, credentials, util, context }) => { | ||
await verifySession(app, "new passphrase"); | ||
const dialog = await util.openEncryptionTab(); | ||
|
||
// The user can only change the recovery key | ||
const changeButton = dialog.getByRole("button", { name: "Change recovery key" }); | ||
await expect(changeButton).toBeVisible(); | ||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("default-recovery.png"); | ||
await changeButton.click(); | ||
|
||
// Display the new recovery key and click on the copy button | ||
await expect(dialog.getByText("Change recovery key?")).toBeVisible(); | ||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("change-key-1-encryption-tab.png", { | ||
mask: [dialog.getByTestId("recoveryKey")], | ||
}); | ||
await dialog.getByRole("button", { name: "Copy" }).click(); | ||
await dialog.getByRole("button", { name: "Continue" }).click(); | ||
|
||
// Confirm the recovery key | ||
await util.confirmRecoveryKey( | ||
"Enter your new recovery key", | ||
"Confirm new recovery key", | ||
"change-key-2-encryption-tab.png", | ||
); | ||
}, | ||
); | ||
|
||
test("should setup the recovery key", { tag: "@screenshot" }, async ({ page, app, util }) => { | ||
await verifySession(app, "new passphrase"); | ||
await util.removeSecretStorageDefaultKeyId(); | ||
|
||
// The key backup is deleted and the user needs to set it up | ||
const dialog = await util.openEncryptionTab(); | ||
const setupButton = dialog.getByRole("button", { name: "Set up recovery" }); | ||
await expect(setupButton).toBeVisible(); | ||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("set-up-recovery.png"); | ||
await setupButton.click(); | ||
|
||
// Display an informative panel about the recovery key | ||
await expect(dialog.getByRole("heading", { name: "Set up recovery" })).toBeVisible(); | ||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("set-up-key-1-encryption-tab.png"); | ||
await dialog.getByRole("button", { name: "Continue" }).click(); | ||
|
||
// Display the new recovery key and click on the copy button | ||
await expect(dialog.getByText("Save your recovery key somewhere safe")).toBeVisible(); | ||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("set-up-key-2-encryption-tab.png", { | ||
mask: [dialog.getByTestId("recoveryKey")], | ||
}); | ||
await dialog.getByRole("button", { name: "Copy" }).click(); | ||
await dialog.getByRole("button", { name: "Continue" }).click(); | ||
|
||
// Confirm the recovery key | ||
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(); | ||
|
||
await app.closeDialog(); | ||
// Check that the current device is connected to key backup and the backup version is the expected one | ||
await checkDeviceIsConnectedKeyBackup(page, "1", true); | ||
}); | ||
|
||
// Test what happens if the cross-signing secrets are in secret storage but are not cached in the local DB. | ||
// | ||
// This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets. | ||
// We simulate this case by deleting the cached secrets in the indexedDB. | ||
test( | ||
"should enter the recovery key when the secrets are not cached", | ||
{ tag: "@screenshot" }, | ||
async ({ page, app, util }) => { | ||
await verifySession(app, "new passphrase"); | ||
// We need to delete the cached secrets | ||
await deleteCachedSecrets(page); | ||
|
||
await util.openEncryptionTab(); | ||
// We ask the user to enter the recovery key | ||
const dialog = util.getEncryptionTabContent(); | ||
const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" }); | ||
await expect(enterKeyButton).toBeVisible(); | ||
await expect(dialog).toMatchScreenshot("out-of-sync-recovery.png"); | ||
await enterKeyButton.click(); | ||
|
||
// Fill the recovery key | ||
await util.enterRecoveryKey(recoveryKey); | ||
await expect(dialog).toMatchScreenshot("default-recovery.png"); | ||
|
||
// Check that our device is now cross-signed | ||
await checkDeviceIsCrossSigned(app); | ||
|
||
// Check that the current device is connected to key backup | ||
// The backup decryption key should be in cache also, as we got it directly from the 4S | ||
await app.closeDialog(); | ||
await checkDeviceIsConnectedKeyBackup(page, expectedBackupVersion, true); | ||
}, | ||
); | ||
}); | ||
|
||
/** | ||
* Remove the cached secrets from the indexedDB | ||
* This is a workaround to simulate the case where the secrets are not cached. | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
async function deleteCachedSecrets(page: Page) { | ||
await page.evaluate(async () => { | ||
const removeCachedSecrets = new Promise((resolve) => { | ||
const request = window.indexedDB.open("matrix-js-sdk::matrix-sdk-crypto"); | ||
request.onsuccess = async (event: Event & { target: { result: IDBDatabase } }) => { | ||
const db = event.target.result; | ||
const request = db.transaction("core", "readwrite").objectStore("core").delete("private_identity"); | ||
request.onsuccess = () => { | ||
db.close(); | ||
resolve(undefined); | ||
}; | ||
}; | ||
}); | ||
await removeCachedSecrets; | ||
}); | ||
await page.reload(); | ||
} |
Binary file modified
BIN
+1.66 KB
(100%)
playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
-179 Bytes
(99%)
...pshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+1.7 KB
(100%)
...user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+1.34 KB
(100%)
...ce-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+29.2 KB
...ings/encryption-user-tab/recovery.spec.ts/change-key-1-encryption-tab-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+28.1 KB
...ings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+17.2 KB
...pshots/settings/encryption-user-tab/recovery.spec.ts/default-recovery-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+21.8 KB
...ts/settings/encryption-user-tab/recovery.spec.ts/out-of-sync-recovery-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+33.4 KB
...ings/encryption-user-tab/recovery.spec.ts/set-up-key-1-encryption-tab-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+32.4 KB
...ings/encryption-user-tab/recovery.spec.ts/set-up-key-2-encryption-tab-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+27.8 KB
...ings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+17.5 KB
...apshots/settings/encryption-user-tab/recovery.spec.ts/set-up-recovery-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+12.7 KB
...ngs/encryption-user-tab/recovery.spec.ts/verify-device-encryption-tab-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+519 Bytes
(100%)
...b.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.