1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-07 23:02:56 +03:00

Element-R: Implement VerificationRequest.{timeout,pending} (#3532)

* implement `VerificationRequest.pending`

* Implement `VerificationRequest.timeout`

* Rust crypto: allow using a memory store (#3536)

* Rust crypto: allow using a memory store

It turns out that, for some usecases (in particular, "bot users" for cypress
tests), we don't need persistent storage and an in-memory store will be fine.

* Rust crypto: use a memory store for the unit tests
This commit is contained in:
Richard van der Hoff
2023-07-03 12:27:38 +01:00
committed by GitHub
parent 3a8a1389f5
commit 3a694f4998
8 changed files with 85 additions and 25 deletions

View File

@@ -55,7 +55,7 @@
], ],
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.11", "@matrix-org/matrix-sdk-crypto-js": "^0.1.0",
"another-json": "^0.2.0", "another-json": "^0.2.0",
"bs58": "^5.0.0", "bs58": "^5.0.0",
"content-type": "^1.0.4", "content-type": "^1.0.4",

View File

@@ -153,6 +153,9 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
expect(request.chosenMethod).toBe(null); // nothing chosen yet expect(request.chosenMethod).toBe(null); // nothing chosen yet
expect(request.initiatedByMe).toBe(true); expect(request.initiatedByMe).toBe(true);
expect(request.otherUserId).toEqual(TEST_USER_ID); expect(request.otherUserId).toEqual(TEST_USER_ID);
expect(request.pending).toBe(true);
// we're using fake timers, so the timeout should have exactly 10 minutes left still.
expect(request.timeout).toEqual(600_000);
// and now the request should be visible via `getVerificationRequestsToDeviceInProgress` // and now the request should be visible via `getVerificationRequestsToDeviceInProgress`
{ {
@@ -248,6 +251,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
// ... and the whole thing should be done! // ... and the whole thing should be done!
await verificationPromise; await verificationPromise;
expect(request.phase).toEqual(VerificationPhase.Done); expect(request.phase).toEqual(VerificationPhase.Done);
expect(request.pending).toBe(false);
// at this point, cancelling should do nothing. // at this point, cancelling should do nothing.
await request.cancel(); await request.cancel();
@@ -564,6 +568,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
expect(request.otherUserId).toEqual(TEST_USER_ID); expect(request.otherUserId).toEqual(TEST_USER_ID);
expect(request.chosenMethod).toBe(null); // nothing chosen yet expect(request.chosenMethod).toBe(null); // nothing chosen yet
expect(canAcceptVerificationRequest(request)).toBe(true); expect(canAcceptVerificationRequest(request)).toBe(true);
expect(request.pending).toBe(true);
// Alice accepts, by sending a to-device message // Alice accepts, by sending a to-device message
const sendToDevicePromise = expectSendToDeviceMessage("m.key.verification.ready"); const sendToDevicePromise = expectSendToDeviceMessage("m.key.verification.ready");

View File

@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import "fake-indexeddb/auto";
import { IDBFactory } from "fake-indexeddb";
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js"; import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
import { KeysQueryRequest, OlmMachine } from "@matrix-org/matrix-sdk-crypto-js"; import { KeysQueryRequest, OlmMachine } from "@matrix-org/matrix-sdk-crypto-js";
import { Mocked } from "jest-mock"; import { Mocked } from "jest-mock";
@@ -41,13 +39,6 @@ import { ServerSideSecretStorage } from "../../../src/secret-storage";
import { CryptoCallbacks, ImportRoomKeysOpts, VerificationRequest } from "../../../src/crypto-api"; import { CryptoCallbacks, ImportRoomKeysOpts, VerificationRequest } from "../../../src/crypto-api";
import * as testData from "../../test-utils/test-data"; import * as testData from "../../test-utils/test-data";
afterEach(() => {
// reset fake-indexeddb after each test, to make sure we don't leak connections
// cf https://github.com/dumbmatter/fakeIndexedDB#wipingresetting-the-indexeddb-for-a-fresh-state
// eslint-disable-next-line no-global-assign
indexedDB = new IDBFactory();
});
const TEST_USER = "@alice:example.com"; const TEST_USER = "@alice:example.com";
const TEST_DEVICE_ID = "TEST_DEVICE"; const TEST_DEVICE_ID = "TEST_DEVICE";
@@ -542,5 +533,5 @@ async function makeTestRustCrypto(
secretStorage: ServerSideSecretStorage = {} as ServerSideSecretStorage, secretStorage: ServerSideSecretStorage = {} as ServerSideSecretStorage,
cryptoCallbacks: CryptoCallbacks = {} as CryptoCallbacks, cryptoCallbacks: CryptoCallbacks = {} as CryptoCallbacks,
): Promise<RustCrypto> { ): Promise<RustCrypto> {
return await initRustCrypto(http, userId, deviceId, secretStorage, cryptoCallbacks); return await initRustCrypto(http, userId, deviceId, secretStorage, cryptoCallbacks, null);
} }

View File

@@ -21,18 +21,49 @@ import { RustVerificationRequest } from "../../../src/rust-crypto/verification";
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor"; import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
describe("VerificationRequest", () => { describe("VerificationRequest", () => {
describe("startVerification", () => { describe("pending", () => {
let request: RustVerificationRequest;
let mockedInner: Mocked<RustSdkCryptoJs.VerificationRequest>; let mockedInner: Mocked<RustSdkCryptoJs.VerificationRequest>;
let mockedOutgoingRequestProcessor: Mocked<OutgoingRequestProcessor>;
beforeEach(() => {
mockedInner = makeMockedInner();
request = makeTestRequest(mockedInner);
});
it("returns true for a created request", () => {
expect(request.pending).toBe(true);
});
it("returns false for passive requests", () => {
mockedInner.isPassive.mockReturnValue(true);
expect(request.pending).toBe(false);
});
it("returns false for completed requests", () => {
mockedInner.phase.mockReturnValue(RustSdkCryptoJs.VerificationRequestPhase.Done);
expect(request.pending).toBe(false);
});
it("returns false for cancelled requests", () => {
mockedInner.phase.mockReturnValue(RustSdkCryptoJs.VerificationRequestPhase.Cancelled);
expect(request.pending).toBe(false);
});
});
describe("timeout", () => {
it("passes through the result", () => {
const mockedInner = makeMockedInner();
const request = makeTestRequest(mockedInner);
mockedInner.timeRemainingMillis.mockReturnValue(10_000);
expect(request.timeout).toEqual(10_000);
});
});
describe("startVerification", () => {
let request: RustVerificationRequest; let request: RustVerificationRequest;
beforeEach(() => { beforeEach(() => {
mockedInner = { request = makeTestRequest();
registerChangesCallback: jest.fn(),
startSas: jest.fn(),
} as unknown as Mocked<RustSdkCryptoJs.VerificationRequest>;
mockedOutgoingRequestProcessor = {} as Mocked<OutgoingRequestProcessor>;
request = new RustVerificationRequest(mockedInner, mockedOutgoingRequestProcessor, undefined);
}); });
it("does not permit methods other than SAS", async () => { it("does not permit methods other than SAS", async () => {
@@ -48,3 +79,24 @@ describe("VerificationRequest", () => {
}); });
}); });
}); });
/** build a RustVerificationRequest with default parameters */
function makeTestRequest(
inner?: RustSdkCryptoJs.VerificationRequest,
outgoingRequestProcessor?: OutgoingRequestProcessor,
): RustVerificationRequest {
inner ??= makeMockedInner();
outgoingRequestProcessor ??= {} as OutgoingRequestProcessor;
return new RustVerificationRequest(inner, outgoingRequestProcessor, undefined);
}
/** Mock up a rust-side VerificationRequest */
function makeMockedInner(): Mocked<RustSdkCryptoJs.VerificationRequest> {
return {
registerChangesCallback: jest.fn(),
startSas: jest.fn(),
phase: jest.fn().mockReturnValue(RustSdkCryptoJs.VerificationRequestPhase.Created),
isPassive: jest.fn().mockReturnValue(false),
timeRemainingMillis: jest.fn(),
} as unknown as Mocked<RustSdkCryptoJs.VerificationRequest>;
}

View File

@@ -2206,10 +2206,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* *
* @experimental * @experimental
* *
* @param useIndexedDB - True to use an indexeddb store, false to use an in-memory store. Defaults to 'true'.
*
* @returns a Promise which will resolve when the crypto layer has been * @returns a Promise which will resolve when the crypto layer has been
* successfully initialised. * successfully initialised.
*/ */
public async initRustCrypto(): Promise<void> { public async initRustCrypto({ useIndexedDB = true }: { useIndexedDB?: boolean } = {}): Promise<void> {
if (this.cryptoBackend) { if (this.cryptoBackend) {
logger.warn("Attempt to re-initialise e2e encryption on MatrixClient"); logger.warn("Attempt to re-initialise e2e encryption on MatrixClient");
return; return;
@@ -2239,6 +2241,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
deviceId, deviceId,
this.secretStorage, this.secretStorage,
this.cryptoCallbacks, this.cryptoCallbacks,
useIndexedDB ? RUST_SDK_STORE_PREFIX : null,
); );
rustCrypto.supportedVerificationMethods = this.verificationMethods; rustCrypto.supportedVerificationMethods = this.verificationMethods;

View File

@@ -18,7 +18,6 @@ import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
import { RustCrypto } from "./rust-crypto"; import { RustCrypto } from "./rust-crypto";
import { logger } from "../logger"; import { logger } from "../logger";
import { RUST_SDK_STORE_PREFIX } from "./constants";
import { IHttpOpts, MatrixHttpApi } from "../http-api"; import { IHttpOpts, MatrixHttpApi } from "../http-api";
import { ServerSideSecretStorage } from "../secret-storage"; import { ServerSideSecretStorage } from "../secret-storage";
import { ICryptoCallbacks } from "../crypto"; import { ICryptoCallbacks } from "../crypto";
@@ -32,6 +31,8 @@ import { ICryptoCallbacks } from "../crypto";
* @param deviceId - The local user's Device ID. * @param deviceId - The local user's Device ID.
* @param secretStorage - Interface to server-side secret storage. * @param secretStorage - Interface to server-side secret storage.
* @param cryptoCallbacks - Crypto callbacks provided by the application * @param cryptoCallbacks - Crypto callbacks provided by the application
* @param storePrefix - the prefix to use on the indexeddbs created by rust-crypto.
* If unset, a memory store will be used.
*/ */
export async function initRustCrypto( export async function initRustCrypto(
http: MatrixHttpApi<IHttpOpts & { onlyData: true }>, http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
@@ -39,6 +40,7 @@ export async function initRustCrypto(
deviceId: string, deviceId: string,
secretStorage: ServerSideSecretStorage, secretStorage: ServerSideSecretStorage,
cryptoCallbacks: ICryptoCallbacks, cryptoCallbacks: ICryptoCallbacks,
storePrefix: string | null,
): Promise<RustCrypto> { ): Promise<RustCrypto> {
// initialise the rust matrix-sdk-crypto-js, if it hasn't already been done // initialise the rust matrix-sdk-crypto-js, if it hasn't already been done
await RustSdkCryptoJs.initAsync(); await RustSdkCryptoJs.initAsync();
@@ -51,7 +53,12 @@ export async function initRustCrypto(
logger.info("Init OlmMachine"); logger.info("Init OlmMachine");
// TODO: use the pickle key for the passphrase // TODO: use the pickle key for the passphrase
const olmMachine = await RustSdkCryptoJs.OlmMachine.initialize(u, d, RUST_SDK_STORE_PREFIX, "test pass"); const olmMachine = await RustSdkCryptoJs.OlmMachine.initialize(
u,
d,
storePrefix ?? undefined,
(storePrefix && "test pass") ?? undefined,
);
const rustCrypto = new RustCrypto(olmMachine, http, userId, deviceId, secretStorage, cryptoCallbacks); const rustCrypto = new RustCrypto(olmMachine, http, userId, deviceId, secretStorage, cryptoCallbacks);
await olmMachine.registerRoomKeyUpdatedCallback((sessions: RustSdkCryptoJs.RoomKeyInfo[]) => await olmMachine.registerRoomKeyUpdatedCallback((sessions: RustSdkCryptoJs.RoomKeyInfo[]) =>
rustCrypto.onRoomKeysUpdated(sessions), rustCrypto.onRoomKeysUpdated(sessions),

View File

@@ -145,7 +145,9 @@ export class RustVerificationRequest
* (ie it is in phase `Requested`, `Ready` or `Started`). * (ie it is in phase `Requested`, `Ready` or `Started`).
*/ */
public get pending(): boolean { public get pending(): boolean {
throw new Error("not implemented"); if (this.inner.isPassive()) return false;
const phase = this.phase;
return phase !== VerificationPhase.Done && phase !== VerificationPhase.Cancelled;
} }
/** /**
@@ -170,7 +172,7 @@ export class RustVerificationRequest
* `null` indicates that there is no timeout * `null` indicates that there is no timeout
*/ */
public get timeout(): number | null { public get timeout(): number | null {
throw new Error("not implemented"); return this.inner.timeRemainingMillis();
} }
/** once the phase is Started (and !initiatedByMe) or Ready: common methods supported by both sides */ /** once the phase is Started (and !initiatedByMe) or Ready: common methods supported by both sides */

View File

@@ -1426,7 +1426,7 @@
dependencies: dependencies:
lodash "^4.17.21" lodash "^4.17.21"
"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.11": "@matrix-org/matrix-sdk-crypto-js@^0.1.0":
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0.tgz#766580036d4df12120ded223e13b5640e77db136" resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0.tgz#766580036d4df12120ded223e13b5640e77db136"
integrity sha512-ra/bcFdleC1iRNms2I96UXA0NvQYWpMsHrV5EfJRS7qV1PtnQNvgsvMfjMbkx8QT2ErEmIhsvB5fPCpfp8BSuw== integrity sha512-ra/bcFdleC1iRNms2I96UXA0NvQYWpMsHrV5EfJRS7qV1PtnQNvgsvMfjMbkx8QT2ErEmIhsvB5fPCpfp8BSuw==