From 61e07633df194fe362e4731913ab559fefe0b576 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Thu, 24 Jul 2025 14:35:55 +0100 Subject: [PATCH] Update to matrix-sdk-crypto-wasm 15.1.0, and add new `ShieldStateCode.MismatchedSender` (#4916) * test: add a flushPromises this seems to be needed because `initRustCrypto` now ends up doing slightly less awaiting * Support new `ShieldStateCode.MismatchedSender` * Update to matrix-sdk-crypto-wasm 15.1.0 * Add `waitFor` and use it instead of `flushPromises` * minor lints and fixes * another lint fix --- package.json | 2 +- spec/test-utils/test-utils.ts | 95 +++++++++++++++++++++++ spec/unit/rust-crypto/rust-crypto.spec.ts | 11 ++- src/crypto-api/index.ts | 6 ++ src/rust-crypto/rust-crypto.ts | 6 ++ yarn.lock | 15 ++-- 6 files changed, 122 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index e8add4647..e33b30b94 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ ], "dependencies": { "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-wasm": "^15.0.0", + "@matrix-org/matrix-sdk-crypto-wasm": "^15.1.0", "another-json": "^0.2.0", "bs58": "^6.0.0", "content-type": "^1.0.4", diff --git a/spec/test-utils/test-utils.ts b/spec/test-utils/test-utils.ts index 07d63e577..01104c7b6 100644 --- a/spec/test-utils/test-utils.ts +++ b/spec/test-utils/test-utils.ts @@ -592,3 +592,98 @@ export async function advanceTimersUntil(promise: Promise): Promise { return await promise; } + +export function jestFakeTimersAreEnabled(): boolean { + return Object.prototype.hasOwnProperty.call(setTimeout, "clock"); +} + +/** + * Run `callback` in a loop, until it returns a successful result (i.e. it does not throw), or we reach a timeout + * + * Based on the function of the same name in the {@link https://testing-library.com/docs/dom-testing-library/api-async/#waitfor DOM testing library}. + * + * @param callback - The function to call to check if we can proceed. If it returns a result (including a falsey one), + * `waitFor` returns that result. If it throws, `waitFor` continues to wait. + * + * May return a promise, in which case no further checks are done until the promise resolves. + * + * @param timeout - The time to wait for, overall, in ms. If `callback` still hasn't returned a successful result after + * this time, `waitFor` will throw an error. + * + * Defaults to 1000. + * + * @param interval - How often to call `callback`. Defaults to 50. + */ +export function waitFor( + callback: () => Promise | T, + { + timeout = 1000, + interval = 50, + }: { + timeout?: number; + interval?: number; + } = {}, +): Promise { + return new Promise((resolve, reject) => { + let lastError: any; + let finished = false; + let intervalId: ReturnType | undefined; + let promisePending = false; + + const overallTimeoutTimer = setTimeout(handleTimeout, timeout); + const usingJestFakeTimers = jestFakeTimersAreEnabled(); + if (usingJestFakeTimers) { + checkCallback(); + + while (!finished) { + jest.advanceTimersByTime(interval); + + // Could have timed-out + if (finished) break; + + checkCallback(); + } + } else { + intervalId = setInterval(checkCallback, interval); + checkCallback(); + } + + function checkCallback() { + if (promisePending) { + // still waiting for the previous check + return; + } + + async function doCheck() { + try { + const result = await callback(); + onDone(); + resolve(result); + } catch (error) { + // Save the most recent callback error to reject the promise with it in the event of a timeout + lastError = error; + } + } + + promisePending = true; + doCheck().finally(() => { + promisePending = false; + }); + } + + function onDone(): void { + finished = true; + clearTimeout(overallTimeoutTimer); + if (intervalId !== undefined) clearInterval(intervalId); + } + + function handleTimeout() { + onDone(); + if (lastError) { + reject(lastError); + } else { + reject(new Error("Timed out in waitFor.")); + } + } + }); +} diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index 36387e6cf..b8e14659d 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -47,7 +47,7 @@ import { MemoryCryptoStore, TypedEventEmitter, } from "../../../src"; -import { emitPromise, mkEvent } from "../../test-utils/test-utils"; +import { emitPromise, mkEvent, waitFor } from "../../test-utils/test-utils"; import { type CryptoBackend } from "../../../src/common-crypto/CryptoBackend"; import { type IEventDecryptionResult, type IMegolmSessionData } from "../../../src/@types/crypto"; import { type OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor"; @@ -1113,6 +1113,7 @@ describe("RustCrypto", () => { it.each([ [undefined, undefined, null], + ["Other", -1, EventShieldReason.UNKNOWN], [ "Encrypted by an unverified user.", RustSdkCryptoJs.ShieldStateCode.UnverifiedIdentity, @@ -1139,6 +1140,11 @@ describe("RustCrypto", () => { RustSdkCryptoJs.ShieldStateCode.VerificationViolation, EventShieldReason.VERIFICATION_VIOLATION, ], + [ + "Mismatched sender", + RustSdkCryptoJs.ShieldStateCode.MismatchedSender, + EventShieldReason.MISMATCHED_SENDER, + ], ])("gets the right shield reason (%s)", async (rustReason, rustCode, expectedReason) => { // suppress the warning from the unknown shield reason jest.spyOn(console, "warn").mockImplementation(() => {}); @@ -2302,8 +2308,9 @@ describe("RustCrypto", () => { }); const rustCrypto = await makeTestRustCrypto(makeMatrixHttpApi(), undefined, undefined, secretStorage); + // We have a key backup - expect(await rustCrypto.getActiveSessionBackupVersion()).not.toBeNull(); + await waitFor(async () => expect(await rustCrypto.getActiveSessionBackupVersion()).not.toBeNull()); const authUploadDeviceSigningKeys = jest.fn(); await rustCrypto.resetEncryption(authUploadDeviceSigningKeys); diff --git a/src/crypto-api/index.ts b/src/crypto-api/index.ts index 173ff3176..a7acbf90a 100644 --- a/src/crypto-api/index.ts +++ b/src/crypto-api/index.ts @@ -1355,6 +1355,12 @@ export enum EventShieldReason { * The sender was previously verified but changed their identity. */ VERIFICATION_VIOLATION, + + /** + * The `sender` field on the event does not match the owner of the device + * that established the Megolm session. + */ + MISMATCHED_SENDER, } /** The result of a call to {@link CryptoApi.getOwnDeviceKeys} */ diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 9cb8522d3..863a0e8e9 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -2277,6 +2277,12 @@ function rustEncryptionInfoToJsEncryptionInfo( case RustSdkCryptoJs.ShieldStateCode.VerificationViolation: shieldReason = EventShieldReason.VERIFICATION_VIOLATION; break; + case RustSdkCryptoJs.ShieldStateCode.MismatchedSender: + shieldReason = EventShieldReason.MISMATCHED_SENDER; + break; + default: + shieldReason = EventShieldReason.UNKNOWN; + break; } return { shieldColour, shieldReason }; diff --git a/yarn.lock b/yarn.lock index 6855a7575..34326a060 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1707,10 +1707,10 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@matrix-org/matrix-sdk-crypto-wasm@^15.0.0": - version "15.0.0" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-15.0.0.tgz#5b29ca1c62f3aface9db06d7441d0a9ba2cd3439" - integrity sha512-tzBGf/jugrOw190Na77LljZIQMTSL6SAnZaATKMlb2j1XOfc5Q+bSJTb9ZWBR7TFs0d8K9spcwRHPc4S/7CMYw== +"@matrix-org/matrix-sdk-crypto-wasm@^15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-15.1.0.tgz#653956f5f6daced55a9df3d2c1114eb2c017b528" + integrity sha512-ZsDdjn46J3+VxsDLmaSODuS+qtGZB/i3Cg9tWL1QPNjvAWzNaTHQ7glleByI2PKVBm83aklfuhGKT2MqE1ZsEA== "@matrix-org/olm@3.2.15": version "3.2.15" @@ -5793,7 +5793,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.1: +picomatch@^4.0.1, picomatch@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== @@ -5803,11 +5803,6 @@ picomatch@^4.0.2: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== -picomatch@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" - integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== - pidtree@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c"