-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
) * Refine `SettingsSection` & `SettingsTab` * Add encryption tab * Add recovery section * Add device verification * Rename `Panel` into `State` * Update & add tests to user settings common * Add tests to `RecoveryPanel` * Add tests to `ChangeRecoveryKey` * Update CreateSecretStorageDialog-test snapshot * Add tests to `EncryptionUserSettingsTab` * Update existing screenshots of e2e tests * Add new encryption tab ownership to `@element-hq/element-crypto-web-reviewers` * Add e2e tests * Fix monospace font and add figma link to hardcoded value * Add unit to Icon * Improve e2e doc * Assert that the crypto module is defined * Add classname doc * Fix typo * Use `good` state instead of default * Rename `ChangeRecoveryKey.isSetupFlow` into `ChangeRecoveryKey.userHasKeyBackup` * Move `deleteCachedSecrets` fixture in `recovery.spec.ts` * Use one callback instead of two in `RecoveryPanel` * Fix docs and naming of `utils.createBot` * Fix typo in `RecoveryPanel` * Add more doc to the state of the `EncryptionUserSettingsTab` * Rename `verification_required` into `set_up_encryption` * Update test * ADd new license * Update comments and doc * Assert that `recoveryKey.encodedPrivateKey` is always defined * Add comments to explain how the secrets could be uncached * Use `matrixClient.secretStorage.getDefaultKeyId` instead of `matrixClient.getCrypto().checkKeyBackupAndEnable` to know if we need to set up a recovery key * Update existing screenshot to add encryption tab. * Update tests * Use new labels when changing the recovery key * Fix docs * Don't reset key backup when creating a recovery key * Fix doc
- Loading branch information
1 parent
03a1b48
commit 13913ba
Showing
53 changed files
with
2,931 additions
and
49 deletions.
There are no files selected for viewing
This file contains 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 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 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 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 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. | ||
*/ | ||
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.