1
0
mirror of https://github.com/element-hq/element-web.git synced 2025-08-06 16:22:46 +03:00

Prompt the user when key storage is unexpectedly off (#29912)

* Assert that we set backup_disabled when turning off key storage

* Prompt the user when key storage is unexpectedly off

* Playwright tests for the Turn on key storage toast
This commit is contained in:
Andy Balaam
2025-05-20 13:28:22 +01:00
committed by GitHub
parent 22c7bf346c
commit b539eda4fe
16 changed files with 655 additions and 20 deletions

View File

@@ -24,7 +24,7 @@ import {
} from "matrix-js-sdk/src/crypto-api";
import { type CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange";
import DeviceListener from "../../src/DeviceListener";
import DeviceListener, { BACKUP_DISABLED_ACCOUNT_DATA_KEY } from "../../src/DeviceListener";
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
import * as SetupEncryptionToast from "../../src/toasts/SetupEncryptionToast";
import * as UnverifiedSessionToast from "../../src/toasts/UnverifiedSessionToast";
@@ -118,6 +118,7 @@ describe("DeviceListener", () => {
getDeviceId: jest.fn().mockReturnValue(deviceId),
setAccountData: jest.fn(),
getAccountData: jest.fn(),
getAccountDataFromServer: jest.fn(),
deleteAccountData: jest.fn(),
getCrypto: jest.fn().mockReturnValue(mockCrypto),
secretStorage: {
@@ -309,6 +310,8 @@ describe("DeviceListener", () => {
it("hides setup encryption toast when cross signing and secret storage are ready", async () => {
mockCrypto!.isCrossSigningReady.mockResolvedValue(true);
mockCrypto!.isSecretStorageReady.mockResolvedValue(true);
mockCrypto!.getActiveSessionBackupVersion.mockResolvedValue("1");
await createAndStart();
expect(SetupEncryptionToast.hideToast).toHaveBeenCalled();
});
@@ -377,6 +380,7 @@ describe("DeviceListener", () => {
it("hides the out-of-sync toast when one of the secrets is missing", async () => {
mockCrypto!.isSecretStorageReady.mockResolvedValue(true);
mockCrypto!.getActiveSessionBackupVersion.mockResolvedValue("1");
// First show the toast
mockCrypto!.getCrossSigningStatus.mockResolvedValue({
@@ -414,6 +418,7 @@ describe("DeviceListener", () => {
it("shows set up recovery toast when user has a key backup available", async () => {
// non falsy response
mockCrypto.getKeyBackupInfo.mockResolvedValue({} as unknown as KeyBackupInfo);
mockCrypto.getActiveSessionBackupVersion.mockResolvedValue("1");
mockClient.secretStorage.getDefaultKeyId.mockResolvedValue(null);
await createAndStart();
@@ -444,6 +449,9 @@ describe("DeviceListener", () => {
it("dispatches keybackup event when key backup is not enabled", async () => {
mockCrypto.getActiveSessionBackupVersion.mockResolvedValue(null);
mockClient.getAccountDataFromServer.mockImplementation((eventType) =>
eventType === BACKUP_DISABLED_ACCOUNT_DATA_KEY ? ({ disabled: true } as any) : null,
);
await createAndStart();
expect(mockDispatcher.dispatch).toHaveBeenCalledWith({
action: Action.ReportKeyBackupNotEnabled,
@@ -463,6 +471,137 @@ describe("DeviceListener", () => {
});
});
it("sets backup_disabled account data when we call recordKeyBackupDisabled", async () => {
const instance = await createAndStart();
await instance.recordKeyBackupDisabled();
expect(mockClient.setAccountData).toHaveBeenCalledWith("m.org.matrix.custom.backup_disabled", {
disabled: true,
});
});
describe("when crypto is in use and set up", () => {
beforeEach(() => {
// Encryption is in use
mockClient.getRooms.mockReturnValue([{ roomId: "!room1" }, { roomId: "!room2" }] as unknown as Room[]);
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
// The device is verified
mockCrypto.getDeviceVerificationStatus.mockResolvedValue(
new DeviceVerificationStatus({ crossSigningVerified: true }),
);
});
describe("but key storage is off", () => {
beforeEach(() => {
// There is no active key backup/storage
mockCrypto.getActiveSessionBackupVersion.mockResolvedValue(null);
});
it("shows the 'Turn on key storage' toast if we never explicitly turned off key storage", async () => {
// Given key backup is off but the account data saying we turned it off is not set
// (m.org.matrix.custom.backup_disabled)
mockClient.getAccountData.mockReturnValue(undefined);
// When we launch the DeviceListener
await createAndStart();
// Then the toast is displayed
expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
);
});
it("shows the 'Turn on key storage' toast if we turned on key storage", async () => {
// Given key backup is off but the account data says we turned it on (this should not happen - the
// account data should only be updated if we turn on key storage)
mockClient.getAccountData.mockImplementation((eventType) =>
eventType === BACKUP_DISABLED_ACCOUNT_DATA_KEY
? new MatrixEvent({ content: { disabled: false } })
: undefined,
);
// When we launch the DeviceListener
await createAndStart();
// Then the toast is displayed
expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
);
});
it("does not show the 'Turn on key storage' toast if we turned off key storage", async () => {
// Given key backup is off but the account data saying we turned it off is set
mockClient.getAccountDataFromServer.mockImplementation((eventType) =>
eventType === BACKUP_DISABLED_ACCOUNT_DATA_KEY ? ({ disabled: true } as any) : null,
);
// When we launch the DeviceListener
await createAndStart();
// Then the toast is not displayed
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
);
});
});
describe("and key storage is on", () => {
beforeEach(() => {
// There is an active key backup/storage
mockCrypto.getActiveSessionBackupVersion.mockResolvedValue("1");
});
it("does not show the 'Turn on key storage' toast if we never explicitly turned off key storage", async () => {
// Given key backup is on and the account data saying we turned it off is not set
mockClient.getAccountData.mockReturnValue(undefined);
// When we launch the DeviceListener
await createAndStart();
// Then the toast is not displayed
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
);
});
it("does not show the 'Turn on key storage' toast if we turned on key storage", async () => {
// Given key backup is on and the account data says we turned it on
mockClient.getAccountData.mockImplementation((eventType) =>
eventType === BACKUP_DISABLED_ACCOUNT_DATA_KEY
? new MatrixEvent({ content: { disabled: false } })
: undefined,
);
// When we launch the DeviceListener
await createAndStart();
// Then the toast is not displayed
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
);
});
it("does not show the 'Turn on key storage' toast if we turned off key storage", async () => {
// Given key backup is on but the account data saying we turned it off is set (this should never
// happen - it should only be set when we turn off key storage or dismiss the toast)
mockClient.getAccountData.mockImplementation((eventType) =>
eventType === BACKUP_DISABLED_ACCOUNT_DATA_KEY
? new MatrixEvent({ content: { disabled: true } })
: undefined,
);
// When we launch the DeviceListener
await createAndStart();
// Then the toast is not displayed
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
);
});
});
});
describe("unverified sessions toasts", () => {
const currentDevice = new Device({ deviceId, userId: userId, algorithms: [], keys: new Map() });
const device2 = new Device({ deviceId: "d2", userId: userId, algorithms: [], keys: new Map() });
@@ -997,6 +1136,8 @@ describe("DeviceListener", () => {
});
it("shows the 'set up recovery' toast if user has not set up 4S", async () => {
mockCrypto!.getActiveSessionBackupVersion.mockResolvedValue("1");
await createAndStart();
expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(SetupEncryptionToast.Kind.SET_UP_RECOVERY);