* Add key storage toggle to Encryption settings
* Keys in the acceptable order
* Fix some tests
* Fix import
* Fix toast showing condition
* Fix import order
* Fix playwright tests
* Fix bits lost in merge
* Add key storage delete confirm screen
* Fix hardcoded Element string
* Fix type imports
* Fix tests
* Tests for key storage delete panel
* Fix test
* Type import
* Test for the view model
* Fix type import
* Actually fix type imports
* Test updating
* Add playwright test & clarify slightly confusing comment
* Show the advnced section whatever the state of key storage
* Update screenshots
* Copy css to its own file
* Add missing doc & merge loading states
* Add tsdoc & loading alt text to spinner
* Turn comments into proper tsdoc
* Switch to TypedEventEmitter and remove unnecessary loading state
* Add screenshot
* Use higher level interface
* Merge the two hooks in EncryptionUserSettingsTab
* Remove unused import
* Don't check key backup enabled state separately
as we don't need it for all the screens
* Update snapshot
* Use fixed recovery key function
* Amalgamate duplicated CSS files
* Have "key storage disabled" as a separate state
* Update snapshot
* Fix... bad merge?
* Add backup enabled mock to more tests
* More snapshots
* Use defer util
* Update to use EncryptionCardButtons
* Update snapshots
* Use EncryptionCardEmphasisedContent
* Update snapshots
* Update snapshot
* Try screenshot from CI playwright
* Try playwright screenshots again
* More screenshots
* Rename to match files
* Test that 4S secrets are deleted
* Make description clearer
* Fix typo & move related states together
* Add comment
* More comments
* Fix hook docs
* restoreAllMocks
* Update snapshot
because pulling in upstream has caused IDs to shift
* Switch icon
as apparenty the error icon has changed
* Update snapshot
* Missing copyright
* Re-order states
and also sort out indenting
* Remove phantom space
* Clarify 'button'
* Clarify docs more
* Explain thinking behind updating
* Switch to getActiveBackupVersion
which checks that key backup is happining on this device, which is
consistent with EX.
* Add use of Key Storage Panel
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
* Change key storage panel to be consistent
ie. using getActiveBackupVersion(), and add comment
* Add tsdoc
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
* Use BACKUP_DISABLED_ACCOUNT_DATA_KEY in more places
* Expand doc
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
* Undo random yarn lock change
* Use aggregate method for disabling key storage
in https://github.com/matrix-org/matrix-js-sdk/pull/4742
* Fix tests
* Use key backup status event to update
* Comment formatting
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
* Fix comment & put check inside if statement
* Add comment
* Prettier
* Fix comment
* Update snapshot
Which has gained nowrap due to 917d53a56f
---------
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
145 lines
6.3 KiB
TypeScript
145 lines
6.3 KiB
TypeScript
/*
|
||
* Copyright 2025 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 { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
|
||
|
||
import { test, expect } from ".";
|
||
import {
|
||
checkDeviceIsConnectedKeyBackup,
|
||
checkDeviceIsCrossSigned,
|
||
createBot,
|
||
deleteCachedSecrets,
|
||
verifySession,
|
||
} from "../../crypto/utils";
|
||
|
||
test.describe("Encryption tab", () => {
|
||
test.use({ displayName: "Alice" });
|
||
|
||
let recoveryKey: GeneratedSecretStorageKey;
|
||
let expectedBackupVersion: string;
|
||
|
||
test.beforeEach(async ({ page, homeserver, credentials }) => {
|
||
// The bot bootstraps cross-signing, creates a key backup and sets up a recovery key
|
||
const res = await createBot(page, homeserver, credentials);
|
||
recoveryKey = res.recoveryKey;
|
||
expectedBackupVersion = res.expectedBackupVersion;
|
||
});
|
||
|
||
test(
|
||
"should show a 'Verify this device' button if the device is unverified",
|
||
{ tag: "@screenshot" },
|
||
async ({ page, app, util }) => {
|
||
const dialog = await util.openEncryptionTab();
|
||
const content = util.getEncryptionTabContent();
|
||
|
||
// 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(content).toMatchScreenshot("verify-device-encryption-tab.png");
|
||
await verifyButton.click();
|
||
|
||
await util.verifyDevice(recoveryKey);
|
||
|
||
await expect(content).toMatchScreenshot("default-tab.png", {
|
||
mask: [content.getByTestId("deviceId"), content.getByTestId("sessionKey")],
|
||
});
|
||
|
||
// 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 checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, 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 prompt to enter the recovery key when the secrets are not cached locally",
|
||
{ tag: "@screenshot" },
|
||
async ({ page, app, util }) => {
|
||
await verifySession(app, recoveryKey.encodedPrivateKey);
|
||
// 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-tab.png", {
|
||
mask: [dialog.getByTestId("deviceId"), dialog.getByTestId("sessionKey")],
|
||
});
|
||
|
||
// 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 checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
|
||
},
|
||
);
|
||
|
||
test("should display the reset identity panel when the user clicks on 'Forgot recovery key?'", async ({
|
||
page,
|
||
app,
|
||
util,
|
||
}) => {
|
||
await verifySession(app, recoveryKey.encodedPrivateKey);
|
||
// We need to delete the cached secrets
|
||
await deleteCachedSecrets(page);
|
||
|
||
// The "Key storage is out sync" section is displayed and the user click on the "Forgot recovery key?" button
|
||
await util.openEncryptionTab();
|
||
const dialog = util.getEncryptionTabContent();
|
||
await dialog.getByRole("button", { name: "Forgot recovery key?" }).click();
|
||
|
||
// The user is prompted to reset their identity
|
||
await expect(dialog.getByText("Forgot your recovery key? You’ll need to reset your identity.")).toBeVisible();
|
||
});
|
||
|
||
test("should warn before turning off key storage", { tag: "@screenshot" }, async ({ page, app, util }) => {
|
||
await verifySession(app, recoveryKey.encodedPrivateKey);
|
||
await util.openEncryptionTab();
|
||
|
||
await page.getByRole("checkbox", { name: "Allow key storage" }).click();
|
||
|
||
await expect(
|
||
page.getByRole("heading", { name: "Are you sure you want to turn off key storage and delete it?" }),
|
||
).toBeVisible();
|
||
|
||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("delete-key-storage-confirm.png");
|
||
|
||
const deleteRequestPromises = [
|
||
page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.master")),
|
||
page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.self_signing")),
|
||
page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.user_signing")),
|
||
page.waitForRequest((req) => req.url().endsWith("/account_data/m.megolm_backup.v1")),
|
||
page.waitForRequest((req) => req.url().endsWith("/account_data/m.secret_storage.default_key")),
|
||
page.waitForRequest((req) => req.url().includes("/account_data/m.secret_storage.key.")),
|
||
];
|
||
|
||
await page.getByRole("button", { name: "Delete key storage" }).click();
|
||
|
||
await expect(page.getByRole("checkbox", { name: "Allow key storage" })).not.toBeChecked();
|
||
|
||
for (const prom of deleteRequestPromises) {
|
||
const request = await prom;
|
||
expect(request.method()).toBe("PUT");
|
||
expect(request.postData()).toBe(JSON.stringify({}));
|
||
}
|
||
});
|
||
});
|