Skip to content

Commit

Permalink
Merge branch 'develop' into florianduros/unread/timeline-proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros authored Jan 25, 2024
2 parents 054ec9e + 5bf29ef commit dd78a8f
Show file tree
Hide file tree
Showing 25 changed files with 269 additions and 144 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/downstream-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concurrency:
jobs:
build-element-web:
name: Build element-web
uses: matrix-org/matrix-react-sdk/.github/workflows/element-web.yaml@v3.88.0
uses: matrix-org/matrix-react-sdk/.github/workflows/element-web.yaml@v3.90.0
with:
matrix-js-sdk-sha: ${{ github.sha }}
react-sdk-repository: matrix-org/matrix-react-sdk
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Changes in [30.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v30

## 🚨 BREAKING CHANGES
* Refactor & make base64 functions browser-safe ([\#3818](https://github.com/matrix-org/matrix-js-sdk/pull/3818)).
* `IndexedDBStore.startup()` must be called after using it on `sdk.createClient` now.

## 🦖 Deprecations
* Deprecate `MatrixEvent.toJSON` ([\#3801](https://github.com/matrix-org/matrix-js-sdk/pull/3801)).
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
],
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/matrix-sdk-crypto-wasm": "^4.0.0",
"@matrix-org/matrix-sdk-crypto-wasm": "^4.0.1",
"another-json": "^0.2.0",
"bs58": "^5.0.0",
"content-type": "^1.0.4",
Expand Down Expand Up @@ -117,7 +117,7 @@
"jest-mock": "^29.0.0",
"lint-staged": "^15.0.2",
"matrix-mock-request": "^2.5.0",
"prettier": "3.1.1",
"prettier": "3.2.4",
"rimraf": "^5.0.0",
"ts-node": "^10.9.1",
"typedoc": "^0.24.0",
Expand Down
3 changes: 2 additions & 1 deletion spec/integ/crypto/cross-signing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s

describe("crossSignDevice", () => {
beforeEach(async () => {
jest.useFakeTimers();
// We want to use fake timers, but the wasm bindings of matrix-sdk-crypto rely on a working `queueMicrotask`.
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

// make sure that there is another device which we can sign
e2eKeyResponder.addDeviceKeys(SIGNED_TEST_DEVICE_DATA);
Expand Down
16 changes: 10 additions & 6 deletions spec/integ/crypto/crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1063,8 +1063,9 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
await startClientAndAwaitFirstSync();
const p2pSession = await establishOlmSession(aliceClient, keyReceiver, syncResponder, testOlmAccount);

// We need to fake the timers to advance the time
jest.useFakeTimers();
// We need to fake the timers to advance the time, but the wasm bindings of matrix-sdk-crypto rely on a
// working `queueMicrotask`
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

const syncResponse = getSyncResponse(["@bob:xyz"]);

Expand Down Expand Up @@ -2189,7 +2190,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,

describe("key upload request", () => {
beforeEach(() => {
jest.useFakeTimers();
// We want to use fake timers, but the wasm bindings of matrix-sdk-crypto rely on a working `queueMicrotask`.
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });
});

afterEach(() => {
Expand Down Expand Up @@ -2389,8 +2391,9 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
expect(devicesInfo.get(user)?.size).toBeFalsy();
});

it("Get devices from tacked users", async () => {
jest.useFakeTimers();
it("Get devices from tracked users", async () => {
// We want to use fake timers, but the wasm bindings of matrix-sdk-crypto rely on a working `queueMicrotask`.
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
await startClientAndAwaitFirstSync();
Expand Down Expand Up @@ -2745,7 +2748,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,

describe("Manage Key Backup", () => {
beforeEach(async () => {
jest.useFakeTimers();
// We want to use fake timers, but the wasm bindings of matrix-sdk-crypto rely on a working `queueMicrotask`.
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });
});

afterEach(() => {
Expand Down
3 changes: 2 additions & 1 deletion spec/integ/crypto/megolm-backup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
let e2eKeyResponder: E2EKeyResponder;

beforeEach(async () => {
jest.useFakeTimers();
// We want to use fake timers, but the wasm bindings of matrix-sdk-crypto rely on a working `queueMicrotask`.
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

// anything that we don't have a specific matcher for silently returns a 404
fetchMock.catch(404);
Expand Down
13 changes: 8 additions & 5 deletions spec/integ/crypto/verification.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ import { encodeBase64 } from "../../../src/base64";

// The verification flows use javascript timers to set timeouts. We tell jest to use mock timer implementations
// to ensure that we don't end up with dangling timeouts.
jest.useFakeTimers();
// But the wasm bindings of matrix-sdk-crypto rely on a working `queueMicrotask`.
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

beforeAll(async () => {
// we use the libolm primitives in the test, so init the Olm library
Expand Down Expand Up @@ -743,6 +744,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
expect(toDeviceMessage.transaction_id).toEqual(transactionId);
expect(toDeviceMessage.code).toEqual("m.user");
expect(request.phase).toEqual(VerificationPhase.Cancelled);
expect(request.cancellationCode).toEqual("m.user");
expect(request.cancellingUserId).toEqual("@alice:localhost");
});

it("can cancel during the SAS phase", async () => {
Expand Down Expand Up @@ -1285,7 +1288,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
jest.useFakeTimers();
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

// the backup secret should not be cached
const cachedKey = await aliceClient.getCrypto()!.getSessionBackupPrivateKey();
Expand All @@ -1309,7 +1312,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
jest.useFakeTimers();
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

// the backup secret should not be cached
const cachedKey = await aliceClient.getCrypto()!.getSessionBackupPrivateKey();
Expand All @@ -1334,7 +1337,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
jest.useFakeTimers();
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

// the backup secret should not be cached
const cachedKey = await aliceClient.getCrypto()!.getSessionBackupPrivateKey();
Expand All @@ -1355,7 +1358,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
jest.useFakeTimers();
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

// the backup secret should not be cached
const cachedKey = await aliceClient.getCrypto()!.getSessionBackupPrivateKey();
Expand Down
15 changes: 15 additions & 0 deletions spec/unit/content-repo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ describe("ContentRepo", function () {
);
});

it("should allow redirects when requested on download URLs", function () {
const mxcUri = "mxc://server.name/resourceid";
expect(getHttpUriForMxc(baseUrl, mxcUri, undefined, undefined, undefined, false, true)).toEqual(
baseUrl + "/_matrix/media/v3/download/server.name/resourceid?allow_redirect=true",
);
});

it("should allow redirects when requested on thumbnail URLs", function () {
const mxcUri = "mxc://server.name/resourceid";
expect(getHttpUriForMxc(baseUrl, mxcUri, 32, 32, "scale", false, true)).toEqual(
baseUrl +
"/_matrix/media/v3/thumbnail/server.name/resourceid?width=32&height=32&method=scale&allow_redirect=true",
);
});

it("should return the empty string for null input", function () {
expect(getHttpUriForMxc(null as any, "")).toEqual("");
});
Expand Down
10 changes: 6 additions & 4 deletions spec/unit/crypto/verification/sas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,12 @@ describe("SAS verification", function () {
const origSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = async (type, map) => {
if (type === "m.key.verification.accept") {
macMethod = map.get(alice.client.getUserId()!)?.get(alice.client.deviceId!)
?.message_authentication_code;
keyAgreement = map.get(alice.client.getUserId()!)?.get(alice.client.deviceId!)
?.key_agreement_protocol;
macMethod = map
.get(alice.client.getUserId()!)
?.get(alice.client.deviceId!)?.message_authentication_code;
keyAgreement = map
.get(alice.client.getUserId()!)
?.get(alice.client.deviceId!)?.key_agreement_protocol;
}
return origSendToDevice(type, map);
};
Expand Down
34 changes: 17 additions & 17 deletions spec/unit/matrix-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
RuleId,
IPushRule,
ConditionKind,
getHttpUriForMxc,
} from "../../src";
import { supportsMatrixCall } from "../../src/webrtc/call";
import { makeBeaconEvent } from "../test-utils/beacon";
Expand Down Expand Up @@ -369,6 +370,21 @@ describe("MatrixClient", function () {
client.stopClient();
});

describe("mxcUrlToHttp", () => {
it("should call getHttpUriForMxc", () => {
const mxc = "mxc://server/example";
expect(client.mxcUrlToHttp(mxc)).toBe(getHttpUriForMxc(client.baseUrl, mxc));
expect(client.mxcUrlToHttp(mxc, 32)).toBe(getHttpUriForMxc(client.baseUrl, mxc, 32));
expect(client.mxcUrlToHttp(mxc, 32, 46)).toBe(getHttpUriForMxc(client.baseUrl, mxc, 32, 46));
expect(client.mxcUrlToHttp(mxc, 32, 46, "scale")).toBe(
getHttpUriForMxc(client.baseUrl, mxc, 32, 46, "scale"),
);
expect(client.mxcUrlToHttp(mxc, 32, 46, "scale", false, true)).toBe(
getHttpUriForMxc(client.baseUrl, mxc, 32, 46, "scale", false, true),
);
});
});

describe("timestampToEvent", () => {
const roomId = "!room:server.org";
const eventId = "$eventId:example.org";
Expand Down Expand Up @@ -1434,23 +1450,7 @@ describe("MatrixClient", function () {
const mockRoom = {
getMyMembership: () => "join",
updatePendingEvent: (event: MatrixEvent, status: EventStatus) => event.setStatus(status),
currentState: {
getStateEvents: (eventType, stateKey) => {
if (eventType === EventType.RoomCreate) {
expect(stateKey).toEqual("");
return new MatrixEvent({
content: {
[RoomCreateTypeField]: RoomType.Space,
},
});
} else if (eventType === EventType.RoomEncryption) {
expect(stateKey).toEqual("");
return new MatrixEvent({ content: {} });
} else {
throw new Error("Unexpected event type or state key");
}
},
} as Room["currentState"],
hasEncryptionStateEvent: jest.fn().mockReturnValue(true),
} as unknown as Room;

let event: MatrixEvent;
Expand Down
82 changes: 70 additions & 12 deletions spec/unit/rust-crypto/rust-crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ import { CryptoStore, SecretStorePrivateKeys } from "../../../src/crypto/store/b
const TEST_USER = "@alice:example.com";
const TEST_DEVICE_ID = "TEST_DEVICE";

beforeAll(async () => {
// Load the WASM upfront, before any of the tests. This can take some time, and doing it here means that it gets
// a separate timeout.
await RustSdkCryptoJs.initAsync();
}, 15000);

afterEach(() => {
fetchMock.reset();
jest.restoreAllMocks();
Expand Down Expand Up @@ -161,6 +167,21 @@ describe("initRustCrypto", () => {
});

describe("libolm migration", () => {
let mockStore: RustSdkCryptoJs.StoreHandle;

beforeEach(() => {
// Stub out a bunch of stuff in the Rust library
mockStore = { free: jest.fn() } as unknown as StoreHandle;
jest.spyOn(StoreHandle, "open").mockResolvedValue(mockStore);

jest.spyOn(Migration, "migrateBaseData").mockResolvedValue(undefined);
jest.spyOn(Migration, "migrateOlmSessions").mockResolvedValue(undefined);
jest.spyOn(Migration, "migrateMegolmSessions").mockResolvedValue(undefined);

const testOlmMachine = makeTestOlmMachine();
jest.spyOn(OlmMachine, "initFromStore").mockResolvedValue(testOlmMachine);
});

it("migrates data from a legacy crypto store", async () => {
const PICKLE_KEY = "pickle1234";
const legacyStore = new MemoryCryptoStore();
Expand All @@ -180,17 +201,6 @@ describe("initRustCrypto", () => {
createMegolmSessions(legacyStore, nDevices, nSessionsPerDevice);
await legacyStore.markSessionsNeedingBackup([{ senderKey: pad43("device5"), sessionId: "session5" }]);

// Stub out a bunch of stuff in the Rust library
const mockStore = { free: jest.fn() } as unknown as StoreHandle;
jest.spyOn(StoreHandle, "open").mockResolvedValue(mockStore);

jest.spyOn(Migration, "migrateBaseData").mockResolvedValue(undefined);
jest.spyOn(Migration, "migrateOlmSessions").mockResolvedValue(undefined);
jest.spyOn(Migration, "migrateMegolmSessions").mockResolvedValue(undefined);

const testOlmMachine = makeTestOlmMachine();
jest.spyOn(OlmMachine, "initFromStore").mockResolvedValue(testOlmMachine);

fetchMock.get("path:/_matrix/client/v3/room_keys/version", { version: "45" });

function legacyMigrationProgressListener(progress: number, total: number): void {
Expand Down Expand Up @@ -269,12 +279,59 @@ describe("initRustCrypto", () => {
expect(session.senderKey).toEqual(pad43(`device${i}`));
expect(session.pickle).toEqual("sessionPickle");
expect(session.roomId!.toString()).toEqual("!room:id");
expect(session.senderSigningKey).toEqual("sender_signing_key");

// only one of the sessions needs backing up
expect(session.backedUp).toEqual(i !== 5 || j !== 5);
}
}
}, 10000);

it("handles megolm sessions with no `keysClaimed`", async () => {
const legacyStore = new MemoryCryptoStore();
legacyStore.storeAccount({}, "not a real account");

legacyStore.storeEndToEndInboundGroupSession(
pad43(`device1`),
`session1`,
{
forwardingCurve25519KeyChain: [],
room_id: "!room:id",
session: "sessionPickle",
},
undefined,
);

const PICKLE_KEY = "pickle1234";
await initRustCrypto({
logger,
http: makeMatrixHttpApi(),
userId: TEST_USER,
deviceId: TEST_DEVICE_ID,
secretStorage: {} as ServerSideSecretStorage,
cryptoCallbacks: {} as CryptoCallbacks,
storePrefix: "storePrefix",
storePassphrase: "storePassphrase",
legacyCryptoStore: legacyStore,
legacyPickleKey: PICKLE_KEY,
});

expect(Migration.migrateMegolmSessions).toHaveBeenCalledTimes(1);
expect(Migration.migrateMegolmSessions).toHaveBeenCalledWith(
expect.any(Array),
new Uint8Array(Buffer.from(PICKLE_KEY)),
mockStore,
);
const megolmSessions: PickledInboundGroupSession[] = mocked(Migration.migrateMegolmSessions).mock
.calls[0][0];
expect(megolmSessions.length).toEqual(1);
const session = megolmSessions[0];
expect(session.senderKey).toEqual(pad43(`device1`));
expect(session.pickle).toEqual("sessionPickle");
expect(session.roomId!.toString()).toEqual("!room:id");
expect(session.senderSigningKey).toBe(undefined);
}, 10000);

async function encryptAndStoreSecretKey(type: string, key: Uint8Array, pickleKey: string, store: CryptoStore) {
const encryptedKey = await encryptAES(encodeBase64(key), Buffer.from(pickleKey), type);
store.storeSecretStorePrivateKey(undefined, type as keyof SecretStorePrivateKeys, encryptedKey);
Expand Down Expand Up @@ -997,7 +1054,8 @@ describe("RustCrypto", () => {
});

it("should wait for a keys/query before returning devices", async () => {
jest.useFakeTimers();
// We want to use fake timers, but the wasm bindings of matrix-sdk-crypto rely on a working `queueMicrotask`.
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

fetchMock.post("path:/_matrix/client/v3/keys/upload", { one_time_key_counts: {} });
fetchMock.post("path:/_matrix/client/v3/keys/query", {
Expand Down
5 changes: 2 additions & 3 deletions src/@types/extensible_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ import { isProvided } from "../extensible_events_v1/utilities";
/**
* Represents the stable and unstable values of a given namespace.
*/
export type TSNamespace<N> = N extends NamespacedValue<infer S, infer U>
? TSNamespaceValue<S> | TSNamespaceValue<U>
: never;
export type TSNamespace<N> =
N extends NamespacedValue<infer S, infer U> ? TSNamespaceValue<S> | TSNamespaceValue<U> : never;

/**
* Represents a namespaced value, if the value is a string. Used to extract provided types
Expand Down
Loading

0 comments on commit dd78a8f

Please sign in to comment.