Add support for device dehydration v2 (#12316)
* rehydrate/dehydrate device if configured in well-known * add handling for dehydrated devices * some fixes * schedule dehydration * improve display of own dehydrated device * created dehydrated device when creating or resetting SSSS * some UI tweaks * reorder strings * lint * remove statement for testing * add playwright test * lint and fix broken test * update to new dehydration API * some fixes from review * try to fix test error * remove unneeded debug line * apply changes from review * add Jest tests * fix typo Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * don't need Object.assign Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
parent
6392759bec
commit
31373399f9
18 changed files with 823 additions and 8 deletions
|
@ -50,6 +50,7 @@ describe("CreateSecretStorageDialog", () => {
|
|||
mockCrypto = mocked(mockClient.getCrypto()!);
|
||||
Object.assign(mockCrypto, {
|
||||
isKeyBackupTrusted: jest.fn(),
|
||||
isDehydrationSupported: jest.fn(() => false),
|
||||
bootstrapCrossSigning: jest.fn(),
|
||||
bootstrapSecretStorage: jest.fn(),
|
||||
});
|
||||
|
|
|
@ -412,6 +412,183 @@ describe("<UserInfo />", () => {
|
|||
await waitFor(() => expect(screen.getByRole("button", { name: "Verify" })).toBeInTheDocument());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("device dehydration", () => {
|
||||
it("hides a verified dehydrated device (unverified user)", async () => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
});
|
||||
const device2 = new Device({
|
||||
deviceId: "d2",
|
||||
userId: defaultUserId,
|
||||
displayName: "dehydrated device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
dehydrated: true,
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[device1.deviceId, device1],
|
||||
[device2.deviceId, device2],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "1 session" });
|
||||
|
||||
// click it
|
||||
await act(() => {
|
||||
return userEvent.click(devicesButton);
|
||||
});
|
||||
|
||||
// there should now be a button with the non-dehydrated device ID
|
||||
expect(screen.getByRole("button", { description: "d1" })).toBeInTheDocument();
|
||||
|
||||
// but not for the dehydrated device ID
|
||||
expect(screen.queryByRole("button", { description: "d2" })).not.toBeInTheDocument();
|
||||
|
||||
// there should be a line saying that the user has "Offline device" enabled
|
||||
expect(screen.getByText("Offline device enabled")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides a verified dehydrated device (verified user)", async () => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
});
|
||||
const device2 = new Device({
|
||||
deviceId: "d2",
|
||||
userId: defaultUserId,
|
||||
displayName: "dehydrated device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
dehydrated: true,
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[device1.deviceId, device1],
|
||||
[device2.deviceId, device2],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, true));
|
||||
mockCrypto.getDeviceVerificationStatus.mockResolvedValue({
|
||||
isVerified: () => true,
|
||||
} as DeviceVerificationStatus);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "1 verified session" });
|
||||
|
||||
// click it
|
||||
await act(() => {
|
||||
return userEvent.click(devicesButton);
|
||||
});
|
||||
|
||||
// there should now be a button with the non-dehydrated device ID
|
||||
expect(screen.getByTitle("d1")).toBeInTheDocument();
|
||||
|
||||
// but not for the dehydrated device ID
|
||||
expect(screen.queryByTitle("d2")).not.toBeInTheDocument();
|
||||
|
||||
// there should be a line saying that the user has "Offline device" enabled
|
||||
expect(screen.getByText("Offline device enabled")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows an unverified dehydrated device", async () => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
});
|
||||
const device2 = new Device({
|
||||
deviceId: "d2",
|
||||
userId: defaultUserId,
|
||||
displayName: "dehydrated device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
dehydrated: true,
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[device1.deviceId, device1],
|
||||
[device2.deviceId, device2],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, true));
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
|
||||
// the dehydrated device should be shown as an unverified device, which means
|
||||
// there should now be a button with the device id ...
|
||||
const deviceButton = screen.getByRole("button", { description: "d2" });
|
||||
|
||||
// ... which should contain the device name
|
||||
expect(within(deviceButton).getByText("dehydrated device")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows dehydrated devices if there is more than one", async () => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "dehydrated device 1",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
dehydrated: true,
|
||||
});
|
||||
const device2 = new Device({
|
||||
deviceId: "d2",
|
||||
userId: defaultUserId,
|
||||
displayName: "dehydrated device 2",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
dehydrated: true,
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[device1.deviceId, device1],
|
||||
[device2.deviceId, device2],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "2 sessions" });
|
||||
|
||||
// click it
|
||||
await act(() => {
|
||||
return userEvent.click(devicesButton);
|
||||
});
|
||||
|
||||
// the dehydrated devices should be shown as an unverified device, which means
|
||||
// there should now be a button with the first dehydrated device id ...
|
||||
const device1Button = screen.getByRole("button", { description: "d1" });
|
||||
|
||||
// ... which should contain the device name
|
||||
expect(within(device1Button).getByText("dehydrated device 1")).toBeInTheDocument();
|
||||
// and a button with the second dehydrated device id ...
|
||||
const device2Button = screen.getByRole("button", { description: "d2" });
|
||||
|
||||
// ... which should contain the device name
|
||||
expect(within(device2Button).getByText("dehydrated device 2")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with an encrypted room", () => {
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
mockClientMethodsDevice,
|
||||
mockPlatformPeg,
|
||||
} from "../../../../../test-utils";
|
||||
import { SDKContext, SdkContextClass } from "../../../../../../src/contexts/SDKContext";
|
||||
|
||||
describe("<SecurityUserSettingsTab />", () => {
|
||||
const defaultProps = {
|
||||
|
@ -44,9 +45,14 @@ describe("<SecurityUserSettingsTab />", () => {
|
|||
getKeyBackupVersion: jest.fn(),
|
||||
});
|
||||
|
||||
const sdkContext = new SdkContextClass();
|
||||
sdkContext.client = mockClient;
|
||||
|
||||
const getComponent = () => (
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<SecurityUserSettingsTab {...defaultProps} />
|
||||
<SDKContext.Provider value={sdkContext}>
|
||||
<SecurityUserSettingsTab {...defaultProps} />
|
||||
</SDKContext.Provider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import { VerificationRequest } from "matrix-js-sdk/src/crypto-api";
|
|||
import { defer, sleep } from "matrix-js-sdk/src/utils";
|
||||
import {
|
||||
ClientEvent,
|
||||
Device,
|
||||
IMyDevice,
|
||||
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
||||
MatrixEvent,
|
||||
|
@ -61,6 +62,18 @@ mockPlatformPeg();
|
|||
// to restore later
|
||||
const realWindowLocation = window.location;
|
||||
|
||||
function deviceToDeviceObj(userId: string, device: IMyDevice, opts: Partial<Device> = {}): Device {
|
||||
const deviceOpts: Pick<Device, "deviceId" | "userId" | "algorithms" | "keys"> & Partial<Device> = {
|
||||
deviceId: device.device_id,
|
||||
userId,
|
||||
algorithms: [],
|
||||
displayName: device.display_name,
|
||||
keys: new Map(),
|
||||
...opts,
|
||||
};
|
||||
return new Device(deviceOpts);
|
||||
}
|
||||
|
||||
describe("<SessionManagerTab />", () => {
|
||||
const aliceId = "@alice:server.org";
|
||||
const deviceId = "alices_device";
|
||||
|
@ -69,10 +82,12 @@ describe("<SessionManagerTab />", () => {
|
|||
device_id: deviceId,
|
||||
display_name: "Alices device",
|
||||
};
|
||||
const alicesDeviceObj = deviceToDeviceObj(aliceId, alicesDevice);
|
||||
const alicesMobileDevice = {
|
||||
device_id: "alices_mobile_device",
|
||||
last_seen_ts: Date.now(),
|
||||
};
|
||||
const alicesMobileDeviceObj = deviceToDeviceObj(aliceId, alicesMobileDevice);
|
||||
|
||||
const alicesOlderMobileDevice = {
|
||||
device_id: "alices_older_mobile_device",
|
||||
|
@ -84,6 +99,20 @@ describe("<SessionManagerTab />", () => {
|
|||
last_seen_ts: Date.now() - (INACTIVE_DEVICE_AGE_MS + 1000),
|
||||
};
|
||||
|
||||
const alicesDehydratedDevice = {
|
||||
device_id: "alices_dehydrated_device",
|
||||
last_seen_ts: Date.now(),
|
||||
};
|
||||
const alicesDehydratedDeviceObj = deviceToDeviceObj(aliceId, alicesDehydratedDevice, { dehydrated: true });
|
||||
|
||||
const alicesOtherDehydratedDevice = {
|
||||
device_id: "alices_other_dehydrated_device",
|
||||
last_seen_ts: Date.now(),
|
||||
};
|
||||
const alicesOtherDehydratedDeviceObj = deviceToDeviceObj(aliceId, alicesOtherDehydratedDevice, {
|
||||
dehydrated: true,
|
||||
});
|
||||
|
||||
const mockVerificationRequest = {
|
||||
cancel: jest.fn(),
|
||||
on: jest.fn(),
|
||||
|
@ -91,6 +120,7 @@ describe("<SessionManagerTab />", () => {
|
|||
|
||||
const mockCrypto = mocked({
|
||||
getDeviceVerificationStatus: jest.fn(),
|
||||
getUserDeviceInfo: jest.fn(),
|
||||
requestDeviceVerification: jest.fn().mockResolvedValue(mockVerificationRequest),
|
||||
} as unknown as CryptoApi);
|
||||
|
||||
|
@ -627,6 +657,137 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("device dehydration", () => {
|
||||
it("Hides a verified dehydrated device", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice, alicesDehydratedDevice],
|
||||
});
|
||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[alicesDeviceObj.deviceId, alicesDeviceObj],
|
||||
[alicesMobileDeviceObj.deviceId, alicesMobileDeviceObj],
|
||||
[alicesDehydratedDeviceObj.deviceId, alicesDehydratedDeviceObj],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[aliceId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
mockCrypto.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||
// alices device is trusted
|
||||
if (deviceId === alicesDevice.device_id) {
|
||||
return new DeviceVerificationStatus({ crossSigningVerified: true, localVerified: true });
|
||||
}
|
||||
// the dehydrated device is trusted
|
||||
if (deviceId === alicesDehydratedDevice.device_id) {
|
||||
return new DeviceVerificationStatus({ crossSigningVerified: true, localVerified: true });
|
||||
}
|
||||
// alices mobile device is not
|
||||
if (deviceId === alicesMobileDevice.device_id) {
|
||||
return new DeviceVerificationStatus({});
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const { queryByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
expect(queryByTestId(`device-tile-${alicesDevice.device_id}`)).toBeTruthy();
|
||||
expect(queryByTestId(`device-tile-${alicesMobileDevice.device_id}`)).toBeTruthy();
|
||||
// the dehydrated device should be hidden
|
||||
expect(queryByTestId(`device-tile-${alicesDehydratedDevice.device_id}`)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("Shows an unverified dehydrated device", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice, alicesDehydratedDevice],
|
||||
});
|
||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[alicesDeviceObj.deviceId, alicesDeviceObj],
|
||||
[alicesMobileDeviceObj.deviceId, alicesMobileDeviceObj],
|
||||
[alicesDehydratedDeviceObj.deviceId, alicesDehydratedDeviceObj],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[aliceId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
mockCrypto.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||
// alices device is trusted
|
||||
if (deviceId === alicesDevice.device_id) {
|
||||
return new DeviceVerificationStatus({ crossSigningVerified: true, localVerified: true });
|
||||
}
|
||||
// the dehydrated device is not
|
||||
if (deviceId === alicesDehydratedDevice.device_id) {
|
||||
return new DeviceVerificationStatus({ crossSigningVerified: false, localVerified: false });
|
||||
}
|
||||
// alices mobile device is not
|
||||
if (deviceId === alicesMobileDevice.device_id) {
|
||||
return new DeviceVerificationStatus({});
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const { queryByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
expect(queryByTestId(`device-tile-${alicesDevice.device_id}`)).toBeTruthy();
|
||||
expect(queryByTestId(`device-tile-${alicesMobileDevice.device_id}`)).toBeTruthy();
|
||||
// the dehydrated device should be shown since it is unverified
|
||||
expect(queryByTestId(`device-tile-${alicesDehydratedDevice.device_id}`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("Shows the dehydrated devices if there are multiple", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice, alicesDehydratedDevice, alicesOtherDehydratedDevice],
|
||||
});
|
||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[alicesDeviceObj.deviceId, alicesDeviceObj],
|
||||
[alicesMobileDeviceObj.deviceId, alicesMobileDeviceObj],
|
||||
[alicesDehydratedDeviceObj.deviceId, alicesDehydratedDeviceObj],
|
||||
[alicesOtherDehydratedDeviceObj.deviceId, alicesOtherDehydratedDeviceObj],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[aliceId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
mockCrypto.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||
// alices device is trusted
|
||||
if (deviceId === alicesDevice.device_id) {
|
||||
return new DeviceVerificationStatus({ crossSigningVerified: true, localVerified: true });
|
||||
}
|
||||
// one dehydrated device is trusted
|
||||
if (deviceId === alicesDehydratedDevice.device_id) {
|
||||
return new DeviceVerificationStatus({ crossSigningVerified: true, localVerified: true });
|
||||
}
|
||||
// the other is not
|
||||
if (deviceId === alicesOtherDehydratedDevice.device_id) {
|
||||
return new DeviceVerificationStatus({ crossSigningVerified: false, localVerified: false });
|
||||
}
|
||||
// alices mobile device is not
|
||||
if (deviceId === alicesMobileDevice.device_id) {
|
||||
return new DeviceVerificationStatus({});
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const { queryByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
expect(queryByTestId(`device-tile-${alicesDevice.device_id}`)).toBeTruthy();
|
||||
expect(queryByTestId(`device-tile-${alicesMobileDevice.device_id}`)).toBeTruthy();
|
||||
// both the dehydrated devices should be shown, since there are multiple
|
||||
expect(queryByTestId(`device-tile-${alicesDehydratedDevice.device_id}`)).toBeTruthy();
|
||||
expect(queryByTestId(`device-tile-${alicesOtherDehydratedDevice.device_id}`)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sign out", () => {
|
||||
it("Signs out of current device", async () => {
|
||||
const modalSpy = jest.spyOn(Modal, "createDialog");
|
||||
|
|
|
@ -40,9 +40,12 @@ describe("SetupEncryptionStore", () => {
|
|||
client = mocked(stubClient());
|
||||
mockCrypto = {
|
||||
bootstrapCrossSigning: jest.fn(),
|
||||
getCrossSigningKeyId: jest.fn(),
|
||||
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
|
||||
getUserDeviceInfo: jest.fn(),
|
||||
getDeviceVerificationStatus: jest.fn(),
|
||||
isDehydrationSupported: jest.fn().mockResolvedValue(false),
|
||||
startDehydration: jest.fn(),
|
||||
} as unknown as Mocked<CryptoApi>;
|
||||
client.getCrypto.mockReturnValue(mockCrypto);
|
||||
|
||||
|
@ -101,7 +104,7 @@ describe("SetupEncryptionStore", () => {
|
|||
expect(setupEncryptionStore.hasDevicesToVerifyAgainst).toBe(true);
|
||||
});
|
||||
|
||||
it("should ignore the dehydrated device", async () => {
|
||||
it("should ignore the MSC2697 dehydrated device", async () => {
|
||||
mockSecretStorage.isStored.mockResolvedValue({ sskeyid: {} as SecretStorageKeyDescriptionAesV1 });
|
||||
|
||||
client.getDehydratedDevice.mockResolvedValue({ device_id: "dehydrated" } as IDehydratedDevice);
|
||||
|
@ -123,6 +126,27 @@ describe("SetupEncryptionStore", () => {
|
|||
expect(mockCrypto.getDeviceVerificationStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should ignore the MSC3812 dehydrated device", async () => {
|
||||
mockSecretStorage.isStored.mockResolvedValue({ sskeyid: {} as SecretStorageKeyDescriptionAesV1 });
|
||||
|
||||
const fakeDevice = new Device({
|
||||
deviceId: "dehydrated",
|
||||
userId: "",
|
||||
algorithms: [],
|
||||
keys: new Map([["curve25519:dehydrated", "identityKey"]]),
|
||||
dehydrated: true,
|
||||
});
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(
|
||||
new Map([[client.getSafeUserId(), new Map([[fakeDevice.deviceId, fakeDevice]])]]),
|
||||
);
|
||||
|
||||
setupEncryptionStore.start();
|
||||
await emitPromise(setupEncryptionStore, "update");
|
||||
|
||||
expect(setupEncryptionStore.hasDevicesToVerifyAgainst).toBe(false);
|
||||
expect(mockCrypto.getDeviceVerificationStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should correctly handle getUserDeviceInfo() returning an empty map", async () => {
|
||||
mockSecretStorage.isStored.mockResolvedValue({ sskeyid: {} as SecretStorageKeyDescriptionAesV1 });
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(new Map());
|
||||
|
@ -133,6 +157,39 @@ describe("SetupEncryptionStore", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("usePassPhrase", () => {
|
||||
it("should use dehydration when enabled", async () => {
|
||||
// mocks for cross-signing and secret storage
|
||||
mockSecretStorage.isStored.mockResolvedValue({ sskeyid: {} as SecretStorageKeyDescriptionAesV1 });
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(new Map());
|
||||
mockCrypto.getDeviceVerificationStatus.mockResolvedValue(
|
||||
new DeviceVerificationStatus({ signedByOwner: true }),
|
||||
);
|
||||
mocked(accessSecretStorage).mockImplementation(async (func?: () => Promise<void>) => {
|
||||
await func!();
|
||||
});
|
||||
|
||||
// mocks for dehydration
|
||||
mockCrypto.isDehydrationSupported.mockResolvedValue(true);
|
||||
const dehydrationPromise = new Promise<void>((resolve) => {
|
||||
// Dehydration gets processed in the background, after
|
||||
// `usePassPhrase` returns, so we need to use a promise to make
|
||||
// sure that it is called.
|
||||
mockCrypto.startDehydration.mockImplementation(async () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
client.waitForClientWellKnown.mockResolvedValue({ "org.matrix.msc3814": true });
|
||||
|
||||
setupEncryptionStore.start();
|
||||
await emitPromise(setupEncryptionStore, "update");
|
||||
|
||||
await setupEncryptionStore.usePassPhrase();
|
||||
|
||||
await dehydrationPromise;
|
||||
});
|
||||
});
|
||||
|
||||
it("resetConfirm should work with a cached account password", async () => {
|
||||
const makeRequest = jest.fn();
|
||||
mockCrypto.bootstrapCrossSigning.mockImplementation(async (opts: IBootstrapCrossSigningOpts) => {
|
||||
|
|
|
@ -115,6 +115,8 @@ export function createTestClient(): MatrixClient {
|
|||
credentials: { userId: "@userId:matrix.org" },
|
||||
bootstrapCrossSigning: jest.fn(),
|
||||
hasSecretStorageKey: jest.fn(),
|
||||
getKeyBackupVersion: jest.fn(),
|
||||
checkOwnCrossSigningTrust: jest.fn(),
|
||||
|
||||
secretStorage: {
|
||||
get: jest.fn(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue