Update key storage toggle when key storage status changes (#30934)
* update key storage toggle when key storage status changes Listen for the CryptoEvent.KeyBackupStatus event and update the state when it changes. * fixup! update key storage toggle when key storage status changes * add comment about handling event
This commit is contained in:
@@ -146,6 +146,29 @@ test.describe("Cryptography", function () {
|
|||||||
}).toPass();
|
}).toPass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// When the user resets their identity, key storage also gets enabled.
|
||||||
|
// Check that the toggle updates to show the correct state.
|
||||||
|
test("Key backup status updates after resetting identity", async ({ page, app, user: aliceCredentials }) => {
|
||||||
|
await app.client.bootstrapCrossSigning(aliceCredentials);
|
||||||
|
|
||||||
|
const encryptionTab = await app.settings.openUserSettings("Encryption");
|
||||||
|
const keyStorageToggle = encryptionTab.getByRole("switch", { name: "Allow key storage" });
|
||||||
|
// Check that key storage starts off as disabled
|
||||||
|
expect(await keyStorageToggle.isChecked()).toBe(false);
|
||||||
|
// Find "the Reset cryptographic identity" button
|
||||||
|
await encryptionTab.getByRole("button", { name: "Reset cryptographic identity" }).click();
|
||||||
|
|
||||||
|
// Confirm
|
||||||
|
await encryptionTab.getByRole("button", { name: "Continue" }).click();
|
||||||
|
|
||||||
|
// Enter the password
|
||||||
|
await page.getByPlaceholder("Password").fill(aliceCredentials.password);
|
||||||
|
await page.getByRole("button", { name: "Continue" }).click();
|
||||||
|
|
||||||
|
// Key storage should now be enabled
|
||||||
|
expect(await keyStorageToggle.isChecked()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
"creating a DM should work, being e2e-encrypted / user verification",
|
"creating a DM should work, being e2e-encrypted / user verification",
|
||||||
{ tag: "@screenshot" },
|
{ tag: "@screenshot" },
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
|
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
|
||||||
import DeviceListener, { BACKUP_DISABLED_ACCOUNT_DATA_KEY } from "../../../../DeviceListener";
|
import DeviceListener, { BACKUP_DISABLED_ACCOUNT_DATA_KEY } from "../../../../DeviceListener";
|
||||||
|
import { useEventEmitterAsyncState } from "../../../../hooks/useEventEmitter";
|
||||||
|
|
||||||
interface KeyStoragePanelState {
|
interface KeyStoragePanelState {
|
||||||
/**
|
/**
|
||||||
@@ -37,31 +39,37 @@ interface KeyStoragePanelState {
|
|||||||
|
|
||||||
/** Returns a ViewModel for use in {@link KeyStoragePanel} and {@link DeleteKeyStoragePanel}. */
|
/** Returns a ViewModel for use in {@link KeyStoragePanel} and {@link DeleteKeyStoragePanel}. */
|
||||||
export function useKeyStoragePanelViewModel(): KeyStoragePanelState {
|
export function useKeyStoragePanelViewModel(): KeyStoragePanelState {
|
||||||
const [isEnabled, setIsEnabled] = useState<boolean | undefined>(undefined);
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
// Whilst the change is being made, the toggle will reflect the pending value rather than the actual state
|
// Whilst the change is being made, the toggle will reflect the pending value rather than the actual state
|
||||||
const [pendingValue, setPendingValue] = useState<boolean | undefined>(undefined);
|
const [pendingValue, setPendingValue] = useState<boolean | undefined>(undefined);
|
||||||
|
|
||||||
const matrixClient = useMatrixClientContext();
|
const matrixClient = useMatrixClientContext();
|
||||||
|
|
||||||
const checkStatus = useCallback(async () => {
|
const isEnabled = useEventEmitterAsyncState(
|
||||||
const crypto = matrixClient.getCrypto();
|
matrixClient,
|
||||||
if (!crypto) {
|
CryptoEvent.KeyBackupStatus,
|
||||||
logger.error("Can't check key backup status: no crypto module available");
|
async (enabled?: boolean) => {
|
||||||
return;
|
// If we're called as a result of an event, rather than during
|
||||||
}
|
// initialisation, we can get the backup status from the event
|
||||||
// The toggle is enabled only if this device will upload megolm keys to the backup.
|
// instead of having to query the backup version.
|
||||||
// This is consistent with EX.
|
if (enabled !== undefined) {
|
||||||
const activeBackupVersion = await crypto.getActiveSessionBackupVersion();
|
return enabled;
|
||||||
setIsEnabled(activeBackupVersion !== null);
|
}
|
||||||
}, [matrixClient]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const crypto = matrixClient.getCrypto();
|
||||||
(async () => {
|
if (!crypto) {
|
||||||
await checkStatus();
|
logger.error("Can't check key backup status: no crypto module available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The toggle is enabled only if this device will upload megolm keys to the backup.
|
||||||
|
// This is consistent with EX.
|
||||||
|
const activeBackupVersion = await crypto.getActiveSessionBackupVersion();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})();
|
return activeBackupVersion !== null;
|
||||||
}, [checkStatus]);
|
},
|
||||||
|
[matrixClient],
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
const setEnabled = useCallback(
|
const setEnabled = useCallback(
|
||||||
async (enable: boolean) => {
|
async (enable: boolean) => {
|
||||||
@@ -121,14 +129,12 @@ export function useKeyStoragePanelViewModel(): KeyStoragePanelState {
|
|||||||
// so this will stop EX turning it back on spontaneously.
|
// so this will stop EX turning it back on spontaneously.
|
||||||
await matrixClient.setAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY, { disabled: true });
|
await matrixClient.setAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY, { disabled: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkStatus();
|
|
||||||
} finally {
|
} finally {
|
||||||
setPendingValue(undefined);
|
setPendingValue(undefined);
|
||||||
DeviceListener.sharedInstance().start(matrixClient);
|
DeviceListener.sharedInstance().start(matrixClient);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setPendingValue, checkStatus, matrixClient],
|
[setPendingValue, matrixClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { isEnabled: pendingValue ?? isEnabled, setEnabled, loading, busy: pendingValue !== undefined };
|
return { isEnabled: pendingValue ?? isEnabled, setEnabled, loading, busy: pendingValue !== undefined };
|
||||||
|
|||||||
+19
-1
@@ -5,9 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { renderHook } from "jest-matrix-react";
|
import { renderHook, waitFor } from "jest-matrix-react";
|
||||||
import { act } from "react";
|
import { act } from "react";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
|
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
|
||||||
|
|
||||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import type { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
import type { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
||||||
@@ -37,6 +38,23 @@ describe("KeyStoragePanelViewModel", () => {
|
|||||||
expect(result.current.busy).toBe(true);
|
expect(result.current.busy).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should update if a KeyBackupStatus event is received", async () => {
|
||||||
|
const { result } = renderHook(
|
||||||
|
() => useKeyStoragePanelViewModel(),
|
||||||
|
withClientContextRenderOptions(matrixClient),
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(result.current.isEnabled).toBe(false));
|
||||||
|
|
||||||
|
const mock = mocked(matrixClient.getCrypto()!.getActiveSessionBackupVersion);
|
||||||
|
mock.mockResolvedValue("1");
|
||||||
|
matrixClient.emit(CryptoEvent.KeyBackupStatus, true);
|
||||||
|
await waitFor(() => expect(result.current.isEnabled).toBe(true));
|
||||||
|
|
||||||
|
mock.mockResolvedValue(null);
|
||||||
|
matrixClient.emit(CryptoEvent.KeyBackupStatus, false);
|
||||||
|
await waitFor(() => expect(result.current.isEnabled).toBe(false));
|
||||||
|
});
|
||||||
|
|
||||||
it("should call resetKeyBackup if there is no backup currently", async () => {
|
it("should call resetKeyBackup if there is no backup currently", async () => {
|
||||||
mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue(null);
|
mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue(null);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user