-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Open
florianduros
wants to merge
18
commits into
develop
Choose a base branch
from
florianduros/encryption-tab
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 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 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
Validating CODEOWNERS rules …
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,118 @@ | ||
/* | ||
* Copyright 2024 New Vector Ltd. | ||
* | ||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only | ||
* 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"); | ||
} | ||
|
||
/** | ||
* Delete the key backup for the given version | ||
* @param backupVersion | ||
*/ | ||
async deleteKeyBackup(backupVersion: string) { | ||
const client = await this.app.client.prepareClient(); | ||
await client.evaluate(async (client, backupVersion) => { | ||
await client.getCrypto()?.deleteKeyBackupVersion(backupVersion); | ||
}, backupVersion); | ||
} | ||
|
||
/** | ||
* Get the security key from the clipboard and fill in the input field | ||
* Then click on the finish button | ||
* @param screenshot | ||
*/ | ||
async confirmRecoveryKey(screenshot: `${string}.png`) { | ||
const dialog = this.getEncryptionTabContent(); | ||
await expect(dialog.getByText("Enter your recovery key to confirm")).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 expect(dialog).toMatchScreenshot("default-recovery.png"); | ||
} | ||
|
||
/** | ||
* Remove the cached secrets from the indexedDB | ||
*/ | ||
async deleteCachedSecrets() { | ||
await this.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 this.page.reload(); | ||
} | ||
} |
140 changes: 140 additions & 0 deletions
140
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,140 @@ | ||
/* | ||
* Copyright 2024 New Vector Ltd. | ||
* | ||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only | ||
* Please see LICENSE files in the repository root for full details. | ||
*/ | ||
|
||
import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api"; | ||
|
||
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("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.deleteKeyBackup(expectedBackupVersion); | ||
|
||
// The key backup is deleted and the user needs to set up it | ||
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("set-up-key-3-encryption-tab.png"); | ||
|
||
await app.closeDialog(); | ||
// Check that the current device is connected to key backup and the backup version is the expected one | ||
await checkDeviceIsConnectedKeyBackup(page, "2", true); | ||
}); | ||
|
||
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 util.deleteCachedSecrets(); | ||
|
||
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); | ||
}, | ||
); | ||
}); |
Binary file modified
BIN
+1.64 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
-232 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
+31.4 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.3 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this test be more along the lines of registering an account and then setting up recovery? Is the bot doing something here to get crypto into a certain state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can if you delete your key backup. For example go to the Security & Privacy tab and click on "Delete backup". Even if we remove this section of Security & Privacy later, an another matrix client can remove it.
The e2e test call
client.getCrypto()?.deleteKeyBackupVersion(backupVersion)
on the last backup version to be in this state.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right, I think I was confusing this with
deleteCachedSecrets()
.