Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca00094e67 | |||
| 9c6d5a6c55 | |||
| b77fe465f7 | |||
| 8d93f49443 | |||
| f03fbed668 | |||
| c84efb57ef | |||
| 49f11578f7 | |||
| 8df4be0939 | |||
| 80cdbe1058 | |||
| 9c62d15447 | |||
| afc70528cc | |||
| f938d10f7b | |||
| 22f0b781ea | |||
| 1bae10c4b2 | |||
| 9b5b533c6f | |||
| c425945353 | |||
| 0545f6df09 | |||
| d14fc426e6 | |||
| 9f30defcd1 | |||
| 9b175a4985 | |||
| 826ea5bc58 |
+1
-1
@@ -74,7 +74,7 @@ module.exports = {
|
||||
"jest/no-standalone-expect": [
|
||||
"error",
|
||||
{
|
||||
additionalTestBlockFunctions: ["beforeAll", "beforeEach", "oldBackendOnly"],
|
||||
additionalTestBlockFunctions: ["beforeAll", "beforeEach", "oldBackendOnly", "newBackendOnly"],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ concurrency:
|
||||
jobs:
|
||||
cypress:
|
||||
name: Cypress
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/cypress.yaml@develop
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/cypress.yaml@v3.73.1
|
||||
permissions:
|
||||
actions: read
|
||||
issues: read
|
||||
|
||||
@@ -19,7 +19,7 @@ concurrency:
|
||||
jobs:
|
||||
build-element-web:
|
||||
name: Build element-web
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/element-web.yaml@develop
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/element-web.yaml@v3.73.1
|
||||
with:
|
||||
matrix-js-sdk-sha: ${{ github.sha }}
|
||||
react-sdk-repository: matrix-org/matrix-react-sdk
|
||||
|
||||
@@ -24,18 +24,16 @@ jobs:
|
||||
|
||||
- name: 🚀 Publish to npm
|
||||
id: npm-publish
|
||||
uses: JS-DevTools/npm-publish@0f451a94170d1699fd50710966d48fb26194d939 # v1
|
||||
uses: JS-DevTools/npm-publish@a25b4180b728b0279fca97d4e5bccf391685aead # v2.2.0
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
access: public
|
||||
tag: next
|
||||
ignore-scripts: false
|
||||
|
||||
- name: 🎖️ Add `latest` dist-tag to final releases
|
||||
if: github.event.release.prerelease == false
|
||||
run: |
|
||||
package=$(cat package.json | jq -er .name)
|
||||
npm dist-tag add "$package@$release" latest
|
||||
if: github.event.release.prerelease == false && steps.npm-publish.outputs.id
|
||||
run: npm dist-tag add "$release" latest
|
||||
env:
|
||||
# JS-DevTools/npm-publish overrides `NODE_AUTH_TOKEN` with `INPUT_TOKEN` in .npmrc
|
||||
INPUT_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
release: ${{ steps.npm-publish.outputs.version }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
release: ${{ steps.npm-publish.outputs.id }}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
Changes in [26.1.0-rc1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v26.1.0-rc1) (2023-06-13)
|
||||
==========================================================================================================
|
||||
Changes in [26.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v26.1.0) (2023-06-20)
|
||||
==================================================================================================
|
||||
|
||||
## 🦖 Deprecations
|
||||
* Introduce a new `Crypto.Verifier` interface, and deprecate direct access to `VerificationBase`, `SAS` and `ReciprocateQRCode` ([\#3414](https://github.com/matrix-org/matrix-js-sdk/pull/3414)).
|
||||
|
||||
+5
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "26.1.0-rc1",
|
||||
"version": "26.1.0",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
@@ -32,8 +32,8 @@
|
||||
"keywords": [
|
||||
"matrix-org"
|
||||
],
|
||||
"main": "./lib/index.js",
|
||||
"browser": "./lib/browser-index.js",
|
||||
"main": "./src/index.ts",
|
||||
"browser": "./src/browser-index.ts",
|
||||
"matrix_src_main": "./src/index.ts",
|
||||
"matrix_src_browser": "./src/browser-index.ts",
|
||||
"matrix_lib_main": "./lib/index.js",
|
||||
@@ -107,7 +107,7 @@
|
||||
"eslint-import-resolver-typescript": "^3.5.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jest": "^27.1.6",
|
||||
"eslint-plugin-jsdoc": "^45.0.0",
|
||||
"eslint-plugin-jsdoc": "^46.0.0",
|
||||
"eslint-plugin-matrix-org": "^1.0.0",
|
||||
"eslint-plugin-tsdoc": "^0.2.17",
|
||||
"eslint-plugin-unicorn": "^47.0.0",
|
||||
@@ -156,6 +156,5 @@
|
||||
"no-rust-crypto": {
|
||||
"src/rust-crypto/index.ts$": "./src/rust-crypto/browserify-index.ts"
|
||||
}
|
||||
},
|
||||
"typings": "./lib/index.d.ts"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import { escapeRegExp } from "../../../src/utils";
|
||||
import { downloadDeviceToJsDevice } from "../../../src/rust-crypto/device-converter";
|
||||
import { flushPromises } from "../../test-utils/flushPromises";
|
||||
import { mockInitialApiRequests } from "../../test-utils/mockEndpoints";
|
||||
import { SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage";
|
||||
|
||||
const ROOM_ID = "!room:id";
|
||||
|
||||
@@ -402,6 +403,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
||||
// oldBackendOnly is an alternative to `it` or `test` which will skip the test if we are running against the
|
||||
// Rust backend. Once we have full support in the rust sdk, it will go away.
|
||||
const oldBackendOnly = backend === "rust-sdk" ? test.skip : test;
|
||||
const newBackendOnly = backend !== "rust-sdk" ? test.skip : test;
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
@@ -2169,4 +2171,163 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bootstrapSecretStorage", () => {
|
||||
/**
|
||||
* Create a fake secret storage key
|
||||
* Async because `bootstrapSecretStorage` expect an async method
|
||||
*/
|
||||
const createSecretStorageKey = jest.fn().mockResolvedValue({
|
||||
keyInfo: {}, // Returning undefined here used to cause a crash
|
||||
privateKey: Uint8Array.of(32, 33),
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a mock to respond to the PUT request `/_matrix/client/r0/user/:userId/account_data/:type`
|
||||
* Resolved when a key is uploaded (ie in `body.content.key`)
|
||||
* https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3useruseridaccount_datatype
|
||||
*/
|
||||
function awaitKeyStoredInAccountData(): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
// This url is called multiple times during the secret storage bootstrap process
|
||||
// When we received the newly generated key, we return it
|
||||
fetchMock.put(
|
||||
"express:/_matrix/client/r0/user/:userId/account_data/:type",
|
||||
(url: string, options: RequestInit) => {
|
||||
const content = JSON.parse(options.body as string);
|
||||
|
||||
if (content.key) {
|
||||
resolve(content.key);
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send in the sync response the provided `secretStorageKey` into the account_data field
|
||||
* The key is set for the `m.secret_storage.default_key` and `m.secret_storage.key.${secretStorageKey}` events
|
||||
* https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3sync
|
||||
* @param secretStorageKey
|
||||
*/
|
||||
function sendSyncResponse(secretStorageKey: string) {
|
||||
syncResponder.sendOrQueueSyncResponse({
|
||||
next_batch: 1,
|
||||
account_data: {
|
||||
events: [
|
||||
{
|
||||
type: "m.secret_storage.default_key",
|
||||
content: {
|
||||
key: secretStorageKey,
|
||||
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
},
|
||||
},
|
||||
// Needed for secretStorage.getKey or secretStorage.hasKey
|
||||
{
|
||||
type: `m.secret_storage.key.${secretStorageKey}`,
|
||||
content: {
|
||||
key: secretStorageKey,
|
||||
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
createSecretStorageKey.mockClear();
|
||||
|
||||
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||
await startClientAndAwaitFirstSync();
|
||||
});
|
||||
|
||||
newBackendOnly("should do no nothing if createSecretStorageKey is not set", async () => {
|
||||
await aliceClient.getCrypto()!.bootstrapSecretStorage({ setupNewSecretStorage: true });
|
||||
|
||||
// No key was created
|
||||
expect(createSecretStorageKey).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
newBackendOnly("should create a new key", async () => {
|
||||
const bootstrapPromise = aliceClient
|
||||
.getCrypto()!
|
||||
.bootstrapSecretStorage({ setupNewSecretStorage: true, createSecretStorageKey });
|
||||
|
||||
// Wait for the key to be uploaded in the account data
|
||||
const secretStorageKey = await awaitKeyStoredInAccountData();
|
||||
|
||||
// Return the newly created key in the sync response
|
||||
sendSyncResponse(secretStorageKey);
|
||||
|
||||
// Finally, wait for bootstrapSecretStorage to finished
|
||||
await bootstrapPromise;
|
||||
|
||||
const defaultKeyId = await aliceClient.secretStorage.getDefaultKeyId();
|
||||
// Check that the uploaded key in stored in the secret storage
|
||||
expect(await aliceClient.secretStorage.hasKey(secretStorageKey)).toBeTruthy();
|
||||
// Check that the uploaded key is the default key
|
||||
expect(defaultKeyId).toBe(secretStorageKey);
|
||||
});
|
||||
|
||||
newBackendOnly(
|
||||
"should do nothing if an AES key is already in the secret storage and setupNewSecretStorage is not set",
|
||||
async () => {
|
||||
const bootstrapPromise = aliceClient.getCrypto()!.bootstrapSecretStorage({ createSecretStorageKey });
|
||||
|
||||
// Wait for the key to be uploaded in the account data
|
||||
const secretStorageKey = await awaitKeyStoredInAccountData();
|
||||
|
||||
// Return the newly created key in the sync response
|
||||
sendSyncResponse(secretStorageKey);
|
||||
|
||||
// Wait for bootstrapSecretStorage to finished
|
||||
await bootstrapPromise;
|
||||
|
||||
// Call again bootstrapSecretStorage
|
||||
await aliceClient.getCrypto()!.bootstrapSecretStorage({ createSecretStorageKey });
|
||||
|
||||
// createSecretStorageKey should be called only on the first run of bootstrapSecretStorage
|
||||
expect(createSecretStorageKey).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
|
||||
newBackendOnly(
|
||||
"should create a new key if setupNewSecretStorage is at true even if an AES key is already in the secret storage",
|
||||
async () => {
|
||||
let bootstrapPromise = aliceClient
|
||||
.getCrypto()!
|
||||
.bootstrapSecretStorage({ setupNewSecretStorage: true, createSecretStorageKey });
|
||||
|
||||
// Wait for the key to be uploaded in the account data
|
||||
let secretStorageKey = await awaitKeyStoredInAccountData();
|
||||
|
||||
// Return the newly created key in the sync response
|
||||
sendSyncResponse(secretStorageKey);
|
||||
|
||||
// Wait for bootstrapSecretStorage to finished
|
||||
await bootstrapPromise;
|
||||
|
||||
// Call again bootstrapSecretStorage
|
||||
bootstrapPromise = aliceClient
|
||||
.getCrypto()!
|
||||
.bootstrapSecretStorage({ setupNewSecretStorage: true, createSecretStorageKey });
|
||||
|
||||
// Wait for the key to be uploaded in the account data
|
||||
secretStorageKey = await awaitKeyStoredInAccountData();
|
||||
|
||||
// Return the newly created key in the sync response
|
||||
sendSyncResponse(secretStorageKey);
|
||||
|
||||
// Wait for bootstrapSecretStorage to finished
|
||||
await bootstrapPromise;
|
||||
|
||||
// createSecretStorageKey should have been called twice, one time every bootstrapSecretStorage call
|
||||
expect(createSecretStorageKey).toHaveBeenCalledTimes(2);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,10 +17,19 @@ limitations under the License.
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
import { MockResponse } from "fetch-mock";
|
||||
|
||||
import { createClient, MatrixClient } from "../../../src";
|
||||
import { ShowQrCodeCallbacks, ShowSasCallbacks, Verifier, VerifierEvent } from "../../../src/crypto-api/verification";
|
||||
import { createClient, CryptoEvent, MatrixClient } from "../../../src";
|
||||
import {
|
||||
canAcceptVerificationRequest,
|
||||
ShowQrCodeCallbacks,
|
||||
ShowSasCallbacks,
|
||||
VerificationPhase,
|
||||
VerificationRequest,
|
||||
VerificationRequestEvent,
|
||||
Verifier,
|
||||
VerifierEvent,
|
||||
} from "../../../src/crypto-api/verification";
|
||||
import { escapeRegExp } from "../../../src/utils";
|
||||
import { CRYPTO_BACKENDS, InitCrypto } from "../../test-utils/test-utils";
|
||||
import { CRYPTO_BACKENDS, emitPromise, InitCrypto } from "../../test-utils/test-utils";
|
||||
import { SyncResponder } from "../../test-utils/SyncResponder";
|
||||
import {
|
||||
MASTER_CROSS_SIGNING_PUBLIC_KEY_BASE64,
|
||||
@@ -31,11 +40,6 @@ import {
|
||||
TEST_USER_ID,
|
||||
} from "../../test-utils/test-data";
|
||||
import { mockInitialApiRequests } from "../../test-utils/mockEndpoints";
|
||||
import {
|
||||
Phase,
|
||||
VerificationRequest,
|
||||
VerificationRequestEvent,
|
||||
} from "../../../src/crypto/verification/request/VerificationRequest";
|
||||
|
||||
// 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.
|
||||
@@ -126,11 +130,11 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
|
||||
// have alice initiate a verification. She should send a m.key.verification.request
|
||||
let [requestBody, request] = await Promise.all([
|
||||
expectSendToDeviceMessage("m.key.verification.request"),
|
||||
aliceClient.requestVerification(TEST_USER_ID, [TEST_DEVICE_ID]),
|
||||
aliceClient.getCrypto()!.requestDeviceVerification(TEST_USER_ID, TEST_DEVICE_ID),
|
||||
]);
|
||||
const transactionId = request.transactionId;
|
||||
expect(transactionId).toBeDefined();
|
||||
expect(request.phase).toEqual(Phase.Requested);
|
||||
expect(request.phase).toEqual(VerificationPhase.Requested);
|
||||
expect(request.roomId).toBeUndefined();
|
||||
|
||||
let toDeviceMessage = requestBody.messages[TEST_USER_ID][TEST_DEVICE_ID];
|
||||
@@ -148,7 +152,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
|
||||
},
|
||||
});
|
||||
await waitForVerificationRequestChanged(request);
|
||||
expect(request.phase).toEqual(Phase.Ready);
|
||||
expect(request.phase).toEqual(VerificationPhase.Ready);
|
||||
expect(request.otherDeviceId).toEqual(TEST_DEVICE_ID);
|
||||
|
||||
// ... and picks a method with m.key.verification.start
|
||||
@@ -165,7 +169,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
|
||||
},
|
||||
});
|
||||
await waitForVerificationRequestChanged(request);
|
||||
expect(request.phase).toEqual(Phase.Started);
|
||||
expect(request.phase).toEqual(VerificationPhase.Started);
|
||||
expect(request.chosenMethod).toEqual("m.sas.v1");
|
||||
|
||||
// there should now be a verifier
|
||||
@@ -238,7 +242,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
|
||||
|
||||
// ... and the whole thing should be done!
|
||||
await verificationPromise;
|
||||
expect(request.phase).toEqual(Phase.Done);
|
||||
expect(request.phase).toEqual(VerificationPhase.Done);
|
||||
|
||||
// we're done with the temporary keypair
|
||||
olmSAS.free();
|
||||
@@ -269,7 +273,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
|
||||
// have alice initiate a verification. She should send a m.key.verification.request
|
||||
const [requestBody, request] = await Promise.all([
|
||||
expectSendToDeviceMessage("m.key.verification.request"),
|
||||
aliceClient.requestVerification(TEST_USER_ID, [TEST_DEVICE_ID]),
|
||||
aliceClient.getCrypto()!.requestDeviceVerification(TEST_USER_ID, TEST_DEVICE_ID),
|
||||
]);
|
||||
const transactionId = request.transactionId;
|
||||
|
||||
@@ -290,7 +294,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
|
||||
},
|
||||
});
|
||||
await waitForVerificationRequestChanged(request);
|
||||
expect(request.phase).toEqual(Phase.Ready);
|
||||
expect(request.phase).toEqual(VerificationPhase.Ready);
|
||||
|
||||
// we should now have QR data we can display
|
||||
const qrCodeBuffer = request.getQRCodeBytes()!;
|
||||
@@ -320,7 +324,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
|
||||
},
|
||||
});
|
||||
await waitForVerificationRequestChanged(request);
|
||||
expect(request.phase).toEqual(Phase.Started);
|
||||
expect(request.phase).toEqual(VerificationPhase.Started);
|
||||
expect(request.chosenMethod).toEqual("m.reciprocate.v1");
|
||||
|
||||
// there should now be a verifier
|
||||
@@ -346,10 +350,53 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
|
||||
|
||||
// ... and the whole thing should be done!
|
||||
await verificationPromise;
|
||||
expect(request.phase).toEqual(Phase.Done);
|
||||
expect(request.phase).toEqual(VerificationPhase.Done);
|
||||
},
|
||||
);
|
||||
|
||||
oldBackendOnly("Incoming verification: can accept", async () => {
|
||||
// expect requests to download our own keys
|
||||
fetchMock.post(new RegExp("/_matrix/client/(r0|v3)/keys/query"), {
|
||||
device_keys: {
|
||||
[TEST_USER_ID]: {
|
||||
[TEST_DEVICE_ID]: SIGNED_TEST_DEVICE_DATA,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const TRANSACTION_ID = "abcd";
|
||||
|
||||
// Initiate the request by sending a to-device message
|
||||
returnToDeviceMessageFromSync({
|
||||
type: "m.key.verification.request",
|
||||
content: {
|
||||
from_device: TEST_DEVICE_ID,
|
||||
methods: ["m.sas.v1"],
|
||||
transaction_id: TRANSACTION_ID,
|
||||
timestamp: Date.now() - 1000,
|
||||
},
|
||||
});
|
||||
const request: VerificationRequest = await emitPromise(aliceClient, CryptoEvent.VerificationRequest);
|
||||
expect(request.transactionId).toEqual(TRANSACTION_ID);
|
||||
expect(request.phase).toEqual(VerificationPhase.Requested);
|
||||
expect(request.roomId).toBeUndefined();
|
||||
expect(canAcceptVerificationRequest(request)).toBe(true);
|
||||
|
||||
// Alice accepts, by sending a to-device message
|
||||
const sendToDevicePromise = expectSendToDeviceMessage("m.key.verification.ready");
|
||||
const acceptPromise = request.accept();
|
||||
expect(canAcceptVerificationRequest(request)).toBe(false);
|
||||
expect(request.phase).toEqual(VerificationPhase.Requested);
|
||||
await acceptPromise;
|
||||
const requestBody = await sendToDevicePromise;
|
||||
expect(request.phase).toEqual(VerificationPhase.Ready);
|
||||
|
||||
const toDeviceMessage = requestBody.messages[TEST_USER_ID][TEST_DEVICE_ID];
|
||||
expect(toDeviceMessage.methods).toContain("m.sas.v1");
|
||||
expect(toDeviceMessage.from_device).toEqual(aliceClient.deviceId);
|
||||
expect(toDeviceMessage.transaction_id).toEqual(TRANSACTION_ID);
|
||||
});
|
||||
|
||||
function returnToDeviceMessageFromSync(ev: { type: string; content: object; sender?: string }): void {
|
||||
ev.sender ??= TEST_USER_ID;
|
||||
syncResponder.sendOrQueueSyncResponse({ to_device: { events: [ev] } });
|
||||
|
||||
@@ -55,6 +55,7 @@ import * as threadUtils from "../test-utils/thread";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../test-utils/client";
|
||||
import { logger } from "../../src/logger";
|
||||
import { IMessageOpts } from "../test-utils/test-utils";
|
||||
import { flushPromises } from "../test-utils/flushPromises";
|
||||
|
||||
describe("Room", function () {
|
||||
const roomId = "!foo:bar";
|
||||
@@ -3388,6 +3389,23 @@ describe("Room", function () {
|
||||
const poll = room.polls.get(pollStartEventId);
|
||||
expect(poll?.pollId).toBe(pollStartEventId);
|
||||
});
|
||||
|
||||
it("removes poll from state when redacted", async () => {
|
||||
const pollStartEvent = makePollStart("1");
|
||||
const events = [pollStartEvent];
|
||||
|
||||
await room.processPollEvents(events);
|
||||
|
||||
expect(room.polls.get(pollStartEvent.getId()!)).toBeTruthy();
|
||||
|
||||
const redactedEvent = new MatrixEvent({ type: "m.room.redaction" });
|
||||
pollStartEvent.makeRedacted(redactedEvent);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
// removed from poll state
|
||||
expect(room.polls.get(pollStartEvent.getId()!)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("findPredecessorRoomId", () => {
|
||||
|
||||
@@ -19,16 +19,26 @@ import { IDBFactory } from "fake-indexeddb";
|
||||
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
|
||||
import { KeysQueryRequest, OlmMachine } from "@matrix-org/matrix-sdk-crypto-js";
|
||||
import { Mocked } from "jest-mock";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import { RustCrypto } from "../../../src/rust-crypto/rust-crypto";
|
||||
import { initRustCrypto } from "../../../src/rust-crypto";
|
||||
import { IHttpOpts, IToDeviceEvent, MatrixClient, MatrixHttpApi } from "../../../src";
|
||||
import {
|
||||
HttpApiEvent,
|
||||
HttpApiEventHandlerMap,
|
||||
IHttpOpts,
|
||||
IToDeviceEvent,
|
||||
MatrixClient,
|
||||
MatrixHttpApi,
|
||||
TypedEventEmitter,
|
||||
} from "../../../src";
|
||||
import { mkEvent } from "../../test-utils/test-utils";
|
||||
import { CryptoBackend } from "../../../src/common-crypto/CryptoBackend";
|
||||
import { IEventDecryptionResult } from "../../../src/@types/crypto";
|
||||
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||
import { OutgoingRequest, OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||
import { ServerSideSecretStorage } from "../../../src/secret-storage";
|
||||
import { ImportRoomKeysOpts } from "../../../src/crypto-api";
|
||||
import { CryptoCallbacks, ImportRoomKeysOpts } from "../../../src/crypto-api";
|
||||
import * as testData from "../../test-utils/test-data";
|
||||
|
||||
afterEach(() => {
|
||||
// reset fake-indexeddb after each test, to make sure we don't leak connections
|
||||
@@ -211,6 +221,7 @@ describe("RustCrypto", () => {
|
||||
TEST_USER,
|
||||
TEST_DEVICE_ID,
|
||||
{} as ServerSideSecretStorage,
|
||||
{} as CryptoCallbacks,
|
||||
);
|
||||
rustCrypto["outgoingRequestProcessor"] = outgoingRequestProcessor;
|
||||
});
|
||||
@@ -333,6 +344,7 @@ describe("RustCrypto", () => {
|
||||
TEST_USER,
|
||||
TEST_DEVICE_ID,
|
||||
{} as ServerSideSecretStorage,
|
||||
{} as CryptoCallbacks,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -356,6 +368,99 @@ describe("RustCrypto", () => {
|
||||
expect(res).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("userHasCrossSigningKeys", () => {
|
||||
let rustCrypto: RustCrypto;
|
||||
|
||||
beforeEach(async () => {
|
||||
rustCrypto = await makeTestRustCrypto(undefined, testData.TEST_USER_ID);
|
||||
});
|
||||
|
||||
it("returns false if there is no cross-signing identity", async () => {
|
||||
await expect(rustCrypto.userHasCrossSigningKeys()).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("returns true if OlmMachine has a cross-signing identity", async () => {
|
||||
// @ts-ignore private field
|
||||
const olmMachine = rustCrypto.olmMachine;
|
||||
|
||||
const outgoingRequests: OutgoingRequest[] = await olmMachine.outgoingRequests();
|
||||
// pick out the KeysQueryRequest, and respond to it with the cross-signing keys
|
||||
const req = outgoingRequests.find((r) => r instanceof KeysQueryRequest)!;
|
||||
await olmMachine.markRequestAsSent(
|
||||
req.id!,
|
||||
req.type,
|
||||
JSON.stringify(testData.SIGNED_CROSS_SIGNING_KEYS_DATA),
|
||||
);
|
||||
|
||||
// ... and we should now have cross-signing keys.
|
||||
await expect(rustCrypto.userHasCrossSigningKeys()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createRecoveryKeyFromPassphrase", () => {
|
||||
let rustCrypto: RustCrypto;
|
||||
|
||||
beforeEach(async () => {
|
||||
rustCrypto = await makeTestRustCrypto();
|
||||
});
|
||||
|
||||
it("should create a recovery key without password", async () => {
|
||||
const recoveryKey = await rustCrypto.createRecoveryKeyFromPassphrase();
|
||||
|
||||
// Expected the encoded private key to have 59 chars
|
||||
expect(recoveryKey.encodedPrivateKey?.length).toBe(59);
|
||||
// Expect the private key to be an Uint8Array with a length of 32
|
||||
expect(recoveryKey.privateKey).toBeInstanceOf(Uint8Array);
|
||||
expect(recoveryKey.privateKey.length).toBe(32);
|
||||
// Expect keyInfo to be empty
|
||||
expect(Object.keys(recoveryKey.keyInfo!).length).toBe(0);
|
||||
});
|
||||
|
||||
it("should create a recovery key with password", async () => {
|
||||
const recoveryKey = await rustCrypto.createRecoveryKeyFromPassphrase("my password");
|
||||
|
||||
// Expected the encoded private key to have 59 chars
|
||||
expect(recoveryKey.encodedPrivateKey?.length).toBe(59);
|
||||
// Expect the private key to be an Uint8Array with a length of 32
|
||||
expect(recoveryKey.privateKey).toBeInstanceOf(Uint8Array);
|
||||
expect(recoveryKey.privateKey.length).toBe(32);
|
||||
// Expect keyInfo.passphrase to be filled
|
||||
expect(recoveryKey.keyInfo?.passphrase?.algorithm).toBe("m.pbkdf2");
|
||||
expect(recoveryKey.keyInfo?.passphrase?.iterations).toBe(500000);
|
||||
});
|
||||
});
|
||||
|
||||
it("should wait for a keys/query before returning devices", async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const mockHttpApi = new MatrixHttpApi(new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>(), {
|
||||
baseUrl: "http://server/",
|
||||
prefix: "",
|
||||
onlyData: true,
|
||||
});
|
||||
fetchMock.post("path:/_matrix/client/v3/keys/upload", { one_time_key_counts: {} });
|
||||
fetchMock.post("path:/_matrix/client/v3/keys/query", {
|
||||
device_keys: {
|
||||
[testData.TEST_USER_ID]: {
|
||||
[testData.TEST_DEVICE_ID]: testData.SIGNED_TEST_DEVICE_DATA,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const rustCrypto = await makeTestRustCrypto(mockHttpApi, testData.TEST_USER_ID);
|
||||
|
||||
// an attempt to fetch the device list should block
|
||||
const devicesPromise = rustCrypto.getUserDeviceInfo([testData.TEST_USER_ID]);
|
||||
|
||||
// ... until a /sync completes, and we trigger the outgoingRequests.
|
||||
rustCrypto.onSyncCompleted({});
|
||||
|
||||
const deviceMap = (await devicesPromise).get(testData.TEST_USER_ID)!;
|
||||
expect(deviceMap.has(TEST_DEVICE_ID)).toBe(true);
|
||||
expect(deviceMap.has(testData.TEST_DEVICE_ID)).toBe(true);
|
||||
rustCrypto.stop();
|
||||
});
|
||||
});
|
||||
|
||||
/** build a basic RustCrypto instance for testing
|
||||
@@ -367,6 +472,7 @@ async function makeTestRustCrypto(
|
||||
userId: string = TEST_USER,
|
||||
deviceId: string = TEST_DEVICE_ID,
|
||||
secretStorage: ServerSideSecretStorage = {} as ServerSideSecretStorage,
|
||||
cryptoCallbacks: CryptoCallbacks = {} as CryptoCallbacks,
|
||||
): Promise<RustCrypto> {
|
||||
return await initRustCrypto(http, userId, deviceId, secretStorage);
|
||||
return await initRustCrypto(http, userId, deviceId, secretStorage, cryptoCallbacks);
|
||||
}
|
||||
|
||||
+17
-2
@@ -2223,7 +2223,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// importing rust-crypto will download the webassembly, so we delay it until we know it will be
|
||||
// needed.
|
||||
const RustCrypto = await import("./rust-crypto");
|
||||
const rustCrypto = await RustCrypto.initRustCrypto(this.http, userId, deviceId, this.secretStorage);
|
||||
const rustCrypto = await RustCrypto.initRustCrypto(
|
||||
this.http,
|
||||
userId,
|
||||
deviceId,
|
||||
this.secretStorage,
|
||||
this.cryptoCallbacks,
|
||||
);
|
||||
this.cryptoBackend = rustCrypto;
|
||||
|
||||
// attach the event listeners needed by RustCrypto
|
||||
@@ -2431,12 +2437,17 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param roomId - the room to use for verification
|
||||
*
|
||||
* @returns the VerificationRequest that is in progress, if any
|
||||
* @deprecated Prefer {@link CryptoApi.findVerificationRequestDMInProgress}.
|
||||
*/
|
||||
public findVerificationRequestDMInProgress(roomId: string): VerificationRequest | undefined {
|
||||
if (!this.cryptoBackend) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
} else if (!this.crypto) {
|
||||
// Hack for element-R to avoid breaking the cypress tests. We can get rid of this once the react-sdk is
|
||||
// updated to use CryptoApi.findVerificationRequestDMInProgress.
|
||||
return undefined;
|
||||
}
|
||||
return this.cryptoBackend.findVerificationRequestDMInProgress(roomId);
|
||||
return this.crypto.findVerificationRequestDMInProgress(roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2445,6 +2456,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param userId - the ID of the user to query
|
||||
*
|
||||
* @returns the VerificationRequests that are in progress
|
||||
* @deprecated Prefer {@link CryptoApi.getVerificationRequestsToDeviceInProgress}.
|
||||
*/
|
||||
public getVerificationRequestsToDeviceInProgress(userId: string): VerificationRequest[] {
|
||||
if (!this.crypto) {
|
||||
@@ -2462,6 +2474,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @returns resolves to a VerificationRequest
|
||||
* when the request has been sent to the other party.
|
||||
*
|
||||
* @deprecated Prefer {@link CryptoApi#requestOwnUserVerification} or {@link CryptoApi#requestDeviceVerification}.
|
||||
*/
|
||||
public requestVerification(userId: string, devices?: string[]): Promise<VerificationRequest> {
|
||||
if (!this.crypto) {
|
||||
@@ -2866,6 +2880,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* - migrates Secure Secret Storage to use the latest algorithm, if an outdated
|
||||
* algorithm is found
|
||||
*
|
||||
* @deprecated Use {@link CryptoApi#bootstrapSecretStorage}.
|
||||
*/
|
||||
public bootstrapSecretStorage(opts: ICreateSecretStorageOpts): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import { CryptoApi } from "../crypto-api";
|
||||
import { CrossSigningInfo, UserTrustLevel } from "../crypto/CrossSigning";
|
||||
import { IEncryptedEventInfo } from "../crypto/api";
|
||||
import { IEventDecryptionResult } from "../@types/crypto";
|
||||
import { VerificationRequest } from "../crypto/verification/request/VerificationRequest";
|
||||
|
||||
/**
|
||||
* Common interface for the crypto implementations
|
||||
@@ -79,15 +78,6 @@ export interface CryptoBackend extends SyncCryptoCallbacks, CryptoApi {
|
||||
*/
|
||||
getEventEncryptionInfo(event: MatrixEvent): IEncryptedEventInfo;
|
||||
|
||||
/**
|
||||
* Finds a DM verification request that is already in progress for the given room id
|
||||
*
|
||||
* @param roomId - the room to use for verification
|
||||
*
|
||||
* @returns the VerificationRequest that is in progress, if any
|
||||
*/
|
||||
findVerificationRequestDMInProgress(roomId: string): VerificationRequest | undefined;
|
||||
|
||||
/**
|
||||
* Get the cross signing information for a given user.
|
||||
*
|
||||
|
||||
+161
-9
@@ -18,13 +18,9 @@ import type { IMegolmSessionData } from "./@types/crypto";
|
||||
import { Room } from "./models/room";
|
||||
import { DeviceMap } from "./models/device";
|
||||
import { UIAuthCallback } from "./interactive-auth";
|
||||
|
||||
/** Types of cross-signing key */
|
||||
export enum CrossSigningKey {
|
||||
Master = "master",
|
||||
SelfSigning = "self_signing",
|
||||
UserSigning = "user_signing",
|
||||
}
|
||||
import { AddSecretStorageKeyOpts, SecretStorageCallbacks, SecretStorageKeyDescription } from "./secret-storage";
|
||||
import { VerificationRequest } from "./crypto-api/verification";
|
||||
import { KeyBackupInfo } from "./crypto-api/keybackup";
|
||||
|
||||
/**
|
||||
* Public interface to the cryptography parts of the js-sdk
|
||||
@@ -195,12 +191,83 @@ export interface CryptoApi {
|
||||
*/
|
||||
isSecretStorageReady(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Bootstrap the secret storage by creating a new secret storage key and store it in the secret storage.
|
||||
*
|
||||
* - Do nothing if an AES key is already stored in the secret storage and `setupNewKeyBackup` is not set;
|
||||
* - Generate a new key {@link GeneratedSecretStorageKey} with `createSecretStorageKey`.
|
||||
* - Store this key in the secret storage and set it as the default key.
|
||||
* - Call `cryptoCallbacks.cacheSecretStorageKey` if provided.
|
||||
*
|
||||
* @param opts - Options object.
|
||||
*/
|
||||
bootstrapSecretStorage(opts: CreateSecretStorageOpts): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get the status of our cross-signing keys.
|
||||
*
|
||||
* @returns The current status of cross-signing keys: whether we have public and private keys cached locally, and whether the private keys are in secret storage.
|
||||
*/
|
||||
getCrossSigningStatus(): Promise<CrossSigningStatus>;
|
||||
|
||||
/**
|
||||
* Create a recovery key (ie, a key suitable for use with server-side secret storage).
|
||||
*
|
||||
* The key can either be based on a user-supplied passphrase, or just created randomly.
|
||||
*
|
||||
* @param password - Optional passphrase string to use to derive the key,
|
||||
* which can later be entered by the user as an alternative to entering the
|
||||
* recovery key itself. If omitted, a key is generated randomly.
|
||||
*
|
||||
* @returns Object including recovery key and server upload parameters.
|
||||
* The private key should be disposed of after displaying to the use.
|
||||
*/
|
||||
createRecoveryKeyFromPassphrase(password?: string): Promise<GeneratedSecretStorageKey>;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Device/User verification
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Returns to-device verification requests that are already in progress for the given user id.
|
||||
*
|
||||
* @param userId - the ID of the user to query
|
||||
*
|
||||
* @returns the VerificationRequests that are in progress
|
||||
*/
|
||||
getVerificationRequestsToDeviceInProgress(userId: string): VerificationRequest[];
|
||||
|
||||
/**
|
||||
* Finds a DM verification request that is already in progress for the given room id
|
||||
*
|
||||
* @param roomId - the room to use for verification
|
||||
*
|
||||
* @returns the VerificationRequest that is in progress, if any
|
||||
*/
|
||||
findVerificationRequestDMInProgress(roomId: string): VerificationRequest | undefined;
|
||||
|
||||
/**
|
||||
* Send a verification request to our other devices.
|
||||
*
|
||||
* If a verification is already in flight, returns it. Otherwise, initiates a new one.
|
||||
*
|
||||
* @returns a VerificationRequest when the request has been sent to the other party.
|
||||
*/
|
||||
requestOwnUserVerification(): Promise<VerificationRequest>;
|
||||
|
||||
/**
|
||||
* Request an interactive verification with the given device.
|
||||
*
|
||||
* If a verification is already in flight, returns it. Otherwise, initiates a new one.
|
||||
*
|
||||
* @param userId - ID of the owner of the device to verify
|
||||
* @param deviceId - ID of the device to verify
|
||||
*
|
||||
* @returns a VerificationRequest when the request has been sent to the other party.
|
||||
*/
|
||||
requestDeviceVerification(userId: string, deviceId: string): Promise<VerificationRequest>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,8 +368,6 @@ export interface ImportRoomKeysOpts {
|
||||
source?: String; // TODO: Enum (backup, file, ??)
|
||||
}
|
||||
|
||||
export * from "./crypto-api/verification";
|
||||
|
||||
/**
|
||||
* The result of a call to {@link CryptoApi.getCrossSigningStatus}.
|
||||
*/
|
||||
@@ -324,3 +389,90 @@ export interface CrossSigningStatus {
|
||||
userSigningKey: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Crypto callbacks provided by the application
|
||||
*/
|
||||
export interface CryptoCallbacks extends SecretStorageCallbacks {
|
||||
getCrossSigningKey?: (keyType: string, pubKey: string) => Promise<Uint8Array | null>;
|
||||
saveCrossSigningKeys?: (keys: Record<string, Uint8Array>) => void;
|
||||
shouldUpgradeDeviceVerifications?: (users: Record<string, any>) => Promise<string[]>;
|
||||
/**
|
||||
* Called by {@link CryptoApi#bootstrapSecretStorage}
|
||||
* @param keyId - secret storage key id
|
||||
* @param keyInfo - secret storage key info
|
||||
* @param key - private key to store
|
||||
*/
|
||||
cacheSecretStorageKey?: (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array) => void;
|
||||
onSecretRequested?: (
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
requestId: string,
|
||||
secretName: string,
|
||||
deviceTrust: DeviceVerificationStatus,
|
||||
) => Promise<string | undefined>;
|
||||
getDehydrationKey?: (
|
||||
keyInfo: SecretStorageKeyDescription,
|
||||
checkFunc: (key: Uint8Array) => void,
|
||||
) => Promise<Uint8Array>;
|
||||
getBackupKey?: () => Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter of {@link CryptoApi#bootstrapSecretStorage}
|
||||
*/
|
||||
export interface CreateSecretStorageOpts {
|
||||
/**
|
||||
* Function called to await a secret storage key creation flow.
|
||||
* @returns Promise resolving to an object with public key metadata, encoded private
|
||||
* recovery key which should be disposed of after displaying to the user,
|
||||
* and raw private key to avoid round tripping if needed.
|
||||
*/
|
||||
createSecretStorageKey?: () => Promise<GeneratedSecretStorageKey>;
|
||||
|
||||
/**
|
||||
* The current key backup object. If passed,
|
||||
* the passphrase and recovery key from this backup will be used.
|
||||
*/
|
||||
keyBackupInfo?: KeyBackupInfo;
|
||||
|
||||
/**
|
||||
* If true, a new key backup version will be
|
||||
* created and the private key stored in the new SSSS store. Ignored if keyBackupInfo
|
||||
* is supplied.
|
||||
*/
|
||||
setupNewKeyBackup?: boolean;
|
||||
|
||||
/**
|
||||
* Reset even if keys already exist.
|
||||
*/
|
||||
setupNewSecretStorage?: boolean;
|
||||
|
||||
/**
|
||||
* Function called to get the user's
|
||||
* current key backup passphrase. Should return a promise that resolves with a Uint8Array
|
||||
* containing the key, or rejects if the key cannot be obtained.
|
||||
*/
|
||||
getKeyBackupPassphrase?: () => Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
/** Types of cross-signing key */
|
||||
export enum CrossSigningKey {
|
||||
Master = "master",
|
||||
SelfSigning = "self_signing",
|
||||
UserSigning = "user_signing",
|
||||
}
|
||||
|
||||
/**
|
||||
* Recovery key created by {@link CryptoApi#createRecoveryKeyFromPassphrase}
|
||||
*/
|
||||
export interface GeneratedSecretStorageKey {
|
||||
keyInfo?: AddSecretStorageKeyOpts;
|
||||
/** The raw generated private key. */
|
||||
privateKey: Uint8Array;
|
||||
/** The generated key, encoded for display to the user per https://spec.matrix.org/v1.7/client-server-api/#key-representation. */
|
||||
encodedPrivateKey?: string;
|
||||
}
|
||||
|
||||
export * from "./crypto-api/verification";
|
||||
export * from "./crypto-api/keybackup";
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ISigned } from "../@types/signed";
|
||||
|
||||
export interface Curve25519AuthData {
|
||||
public_key: string;
|
||||
private_key_salt?: string;
|
||||
private_key_iterations?: number;
|
||||
private_key_bits?: number;
|
||||
}
|
||||
|
||||
export interface Aes256AuthData {
|
||||
iv: string;
|
||||
mac: string;
|
||||
private_key_salt?: string;
|
||||
private_key_iterations?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra info of a recovery key
|
||||
*/
|
||||
export interface KeyBackupInfo {
|
||||
algorithm: string;
|
||||
auth_data: ISigned & (Curve25519AuthData | Aes256AuthData);
|
||||
count?: number;
|
||||
etag?: string;
|
||||
version?: string; // number contained within
|
||||
}
|
||||
@@ -17,6 +17,187 @@ limitations under the License.
|
||||
import { MatrixEvent } from "../models/event";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||
|
||||
/**
|
||||
* An incoming, or outgoing, request to verify a user or a device via cross-signing.
|
||||
*/
|
||||
export interface VerificationRequest
|
||||
extends TypedEventEmitter<VerificationRequestEvent, VerificationRequestEventHandlerMap> {
|
||||
/**
|
||||
* Unique ID for this verification request.
|
||||
*
|
||||
* An ID isn't assigned until the first message is sent, so this may be `undefined` in the early phases.
|
||||
*/
|
||||
get transactionId(): string | undefined;
|
||||
|
||||
/**
|
||||
* For an in-room verification, the ID of the room.
|
||||
*
|
||||
* For to-device verifictions, `undefined`.
|
||||
*/
|
||||
get roomId(): string | undefined;
|
||||
|
||||
/**
|
||||
* True if this request was initiated by the local client.
|
||||
*
|
||||
* For in-room verifications, the initiator is who sent the `m.key.verification.request` event.
|
||||
* For to-device verifications, the initiator is who sent the `m.key.verification.start` event.
|
||||
*/
|
||||
get initiatedByMe(): boolean;
|
||||
|
||||
/** The user id of the other party in this request */
|
||||
get otherUserId(): string;
|
||||
|
||||
/** For verifications via to-device messages: the ID of the other device. Otherwise, undefined. */
|
||||
get otherDeviceId(): string | undefined;
|
||||
|
||||
/** True if the other party in this request is one of this user's own devices. */
|
||||
get isSelfVerification(): boolean;
|
||||
|
||||
/** current phase of the request. */
|
||||
get phase(): VerificationPhase;
|
||||
|
||||
/** True if the request has sent its initial event and needs more events to complete
|
||||
* (ie it is in phase `Requested`, `Ready` or `Started`).
|
||||
*/
|
||||
get pending(): boolean;
|
||||
|
||||
/**
|
||||
* True if we have started the process of sending an `m.key.verification.ready` (but have not necessarily received
|
||||
* the remote echo which causes a transition to {@link VerificationPhase.Ready}.
|
||||
*/
|
||||
get accepting(): boolean;
|
||||
|
||||
/**
|
||||
* True if we have started the process of sending an `m.key.verification.cancel` (but have not necessarily received
|
||||
* the remote echo which causes a transition to {@link VerificationPhase.Cancelled}).
|
||||
*/
|
||||
get declining(): boolean;
|
||||
|
||||
/**
|
||||
* The remaining number of ms before the request will be automatically cancelled.
|
||||
*
|
||||
* `null` indicates that there is no timeout
|
||||
*/
|
||||
get timeout(): number | null;
|
||||
|
||||
/** once the phase is Started (and !initiatedByMe) or Ready: common methods supported by both sides */
|
||||
get methods(): string[];
|
||||
|
||||
/** the method picked in the .start event */
|
||||
get chosenMethod(): string | null;
|
||||
|
||||
/**
|
||||
* Checks whether the other party supports a given verification method.
|
||||
* This is useful when setting up the QR code UI, as it is somewhat asymmetrical:
|
||||
* if the other party supports SCAN_QR, we should show a QR code in the UI, and vice versa.
|
||||
* For methods that need to be supported by both ends, use the `methods` property.
|
||||
*
|
||||
* @param method - the method to check
|
||||
* @returns true if the other party said they supported the method
|
||||
*/
|
||||
otherPartySupportsMethod(method: string): boolean;
|
||||
|
||||
/**
|
||||
* Accepts the request, sending a .ready event to the other party
|
||||
*
|
||||
* @returns Promise which resolves when the event has been sent.
|
||||
*/
|
||||
accept(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Cancels the request, sending a cancellation to the other party
|
||||
*
|
||||
* @param params - Details for the cancellation, including `reason` (defaults to "User declined"), and `code`
|
||||
* (defaults to `m.user`).
|
||||
*
|
||||
* @returns Promise which resolves when the event has been sent.
|
||||
*/
|
||||
cancel(params?: { reason?: string; code?: string }): Promise<void>;
|
||||
|
||||
/**
|
||||
* Create a {@link Verifier} to do this verification via a particular method.
|
||||
*
|
||||
* If a verifier has already been created for this request, returns that verifier.
|
||||
*
|
||||
* This does *not* send the `m.key.verification.start` event - to do so, call {@link Crypto.Verifier#verify} on the
|
||||
* returned verifier.
|
||||
*
|
||||
* If no previous events have been sent, pass in `targetDevice` to set who to direct this request to.
|
||||
*
|
||||
* @param method - the name of the verification method to use.
|
||||
* @param targetDevice - details of where to send the request to.
|
||||
*
|
||||
* @returns The verifier which will do the actual verification.
|
||||
*/
|
||||
beginKeyVerification(method: string, targetDevice?: { userId?: string; deviceId?: string }): Verifier;
|
||||
|
||||
/**
|
||||
* The verifier which is doing the actual verification, once the method has been established.
|
||||
* Only defined when the `phase` is Started.
|
||||
*/
|
||||
get verifier(): Verifier | undefined;
|
||||
|
||||
/**
|
||||
* Get the data for a QR code allowing the other device to verify this one, if it supports it.
|
||||
*
|
||||
* Only set after a .ready if the other party can scan a QR code, otherwise undefined.
|
||||
*/
|
||||
getQRCodeBytes(): Buffer | undefined;
|
||||
|
||||
/**
|
||||
* If this request has been cancelled, the cancellation code (e.g `m.user`) which is responsible for cancelling
|
||||
* this verification.
|
||||
*/
|
||||
get cancellationCode(): string | null;
|
||||
|
||||
/**
|
||||
* The id of the user that cancelled the request.
|
||||
*
|
||||
* Only defined when phase is Cancelled
|
||||
*/
|
||||
get cancellingUserId(): string | undefined;
|
||||
}
|
||||
|
||||
/** Events emitted by {@link VerificationRequest}. */
|
||||
export enum VerificationRequestEvent {
|
||||
/**
|
||||
* Fires whenever the state of the request object has changed.
|
||||
*
|
||||
* There is no payload to the event.
|
||||
*/
|
||||
Change = "change",
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener type map for {@link VerificationRequestEvent}s.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export type VerificationRequestEventHandlerMap = {
|
||||
[VerificationRequestEvent.Change]: () => void;
|
||||
};
|
||||
|
||||
/** The current phase of a verification request. */
|
||||
export enum VerificationPhase {
|
||||
/** Initial state: no event yet exchanged */
|
||||
Unsent = 1,
|
||||
|
||||
/** An `m.key.verification.request` event has been sent or received */
|
||||
Requested,
|
||||
|
||||
/** An `m.key.verification.ready` event has been sent or received, indicating the verification request is accepted. */
|
||||
Ready,
|
||||
|
||||
/** An `m.key.verification.start` event has been sent or received, choosing a verification method */
|
||||
Started,
|
||||
|
||||
/** An `m.key.verification.cancel` event has been sent or received at any time before the `done` event, cancelling the verification request */
|
||||
Cancelled,
|
||||
|
||||
/** An `m.key.verification.done` event has been **sent**, completing the verification request. */
|
||||
Done,
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Verifier` is responsible for performing the verification using a particular method, such as via QR code or SAS
|
||||
* (emojis).
|
||||
@@ -169,3 +350,11 @@ export interface GeneratedSas {
|
||||
* English name.
|
||||
*/
|
||||
export type EmojiMapping = [emoji: string, name: string];
|
||||
|
||||
/**
|
||||
* True if the request is in a state where it can be accepted (ie, that we're in phases {@link VerificationPhase.Unsent}
|
||||
* or {@link VerificationPhase.Requested}, and that we're not in the process of sending a `ready` or `cancel`).
|
||||
*/
|
||||
export function canAcceptVerificationRequest(req: VerificationRequest): boolean {
|
||||
return req.phase < VerificationPhase.Ready && !req.accepting && !req.declining;
|
||||
}
|
||||
|
||||
+5
-43
@@ -15,11 +15,14 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { DeviceInfo } from "./deviceinfo";
|
||||
import { IKeyBackupInfo } from "./keybackup";
|
||||
import type { AddSecretStorageKeyOpts } from "../secret-storage";
|
||||
|
||||
/* re-exports for backwards compatibility. */
|
||||
// CrossSigningKey is used as a value in `client.ts`, we can't export it as a type
|
||||
export { CrossSigningKey } from "../crypto-api";
|
||||
export type {
|
||||
GeneratedSecretStorageKey as IRecoveryKey,
|
||||
CreateSecretStorageOpts as ICreateSecretStorageOpts,
|
||||
} from "../crypto-api";
|
||||
|
||||
export type {
|
||||
ImportRoomKeyProgressData as IImportOpts,
|
||||
@@ -65,44 +68,3 @@ export interface IEncryptedEventInfo {
|
||||
*/
|
||||
mismatchedSender: boolean;
|
||||
}
|
||||
|
||||
export interface IRecoveryKey {
|
||||
keyInfo?: AddSecretStorageKeyOpts;
|
||||
privateKey: Uint8Array;
|
||||
encodedPrivateKey?: string;
|
||||
}
|
||||
|
||||
export interface ICreateSecretStorageOpts {
|
||||
/**
|
||||
* Function called to await a secret storage key creation flow.
|
||||
* @returns Promise resolving to an object with public key metadata, encoded private
|
||||
* recovery key which should be disposed of after displaying to the user,
|
||||
* and raw private key to avoid round tripping if needed.
|
||||
*/
|
||||
createSecretStorageKey?: () => Promise<IRecoveryKey>;
|
||||
|
||||
/**
|
||||
* The current key backup object. If passed,
|
||||
* the passphrase and recovery key from this backup will be used.
|
||||
*/
|
||||
keyBackupInfo?: IKeyBackupInfo;
|
||||
|
||||
/**
|
||||
* If true, a new key backup version will be
|
||||
* created and the private key stored in the new SSSS store. Ignored if keyBackupInfo
|
||||
* is supplied.
|
||||
*/
|
||||
setupNewKeyBackup?: boolean;
|
||||
|
||||
/**
|
||||
* Reset even if keys already exist.
|
||||
*/
|
||||
setupNewSecretStorage?: boolean;
|
||||
|
||||
/**
|
||||
* Function called to get the user's
|
||||
* current key backup passphrase. Should return a promise that resolves with a Uint8Array
|
||||
* containing the key, or rejects if the key cannot be obtained.
|
||||
*/
|
||||
getKeyBackupPassphrase?: () => Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
+13
-21
@@ -80,7 +80,6 @@ import {
|
||||
AccountDataClient,
|
||||
AddSecretStorageKeyOpts,
|
||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
SecretStorageCallbacks,
|
||||
SecretStorageKeyDescription,
|
||||
SecretStorageKeyObject,
|
||||
SecretStorageKeyTuple,
|
||||
@@ -97,7 +96,10 @@ import { Device, DeviceMap } from "../models/device";
|
||||
import { deviceInfoToDevice } from "./device-converter";
|
||||
|
||||
/* re-exports for backwards compatibility */
|
||||
export type { BootstrapCrossSigningOpts as IBootstrapCrossSigningOpts } from "../crypto-api";
|
||||
export type {
|
||||
BootstrapCrossSigningOpts as IBootstrapCrossSigningOpts,
|
||||
CryptoCallbacks as ICryptoCallbacks,
|
||||
} from "../crypto-api";
|
||||
|
||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
@@ -134,25 +136,6 @@ interface IInitOpts {
|
||||
pickleKey?: string;
|
||||
}
|
||||
|
||||
export interface ICryptoCallbacks extends SecretStorageCallbacks {
|
||||
getCrossSigningKey?: (keyType: string, pubKey: string) => Promise<Uint8Array | null>;
|
||||
saveCrossSigningKeys?: (keys: Record<string, Uint8Array>) => void;
|
||||
shouldUpgradeDeviceVerifications?: (users: Record<string, any>) => Promise<string[]>;
|
||||
cacheSecretStorageKey?: (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array) => void;
|
||||
onSecretRequested?: (
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
requestId: string,
|
||||
secretName: string,
|
||||
deviceTrust: DeviceTrustLevel,
|
||||
) => Promise<string | undefined>;
|
||||
getDehydrationKey?: (
|
||||
keyInfo: SecretStorageKeyDescription,
|
||||
checkFunc: (key: Uint8Array) => void,
|
||||
) => Promise<Uint8Array>;
|
||||
getBackupKey?: () => Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
interface IRoomKey {
|
||||
room_id: string;
|
||||
@@ -2356,6 +2339,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
return this.requestVerificationWithChannel(userId, channel, this.inRoomVerificationRequests);
|
||||
}
|
||||
|
||||
/** @deprecated Use `requestOwnUserVerificationToDevice` or `requestDeviceVerification` */
|
||||
public requestVerification(userId: string, devices?: string[]): Promise<VerificationRequest> {
|
||||
if (!devices) {
|
||||
devices = Object.keys(this.deviceList.getRawStoredDevicesForUser(userId));
|
||||
@@ -2368,6 +2352,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
return this.requestVerificationWithChannel(userId, channel, this.toDeviceVerificationRequests);
|
||||
}
|
||||
|
||||
public requestOwnUserVerification(): Promise<VerificationRequest> {
|
||||
return this.requestVerification(this.userId);
|
||||
}
|
||||
|
||||
public requestDeviceVerification(userId: string, deviceId: string): Promise<VerificationRequest> {
|
||||
return this.requestVerification(userId, [deviceId]);
|
||||
}
|
||||
|
||||
private async requestVerificationWithChannel(
|
||||
userId: string,
|
||||
channel: IVerificationChannel,
|
||||
|
||||
+6
-21
@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ISigned } from "../@types/signed";
|
||||
import { IEncryptedPayload } from "./aes";
|
||||
|
||||
export interface Curve25519SessionData {
|
||||
@@ -35,27 +34,13 @@ export interface IKeyBackupRoomSessions {
|
||||
[sessionId: string]: IKeyBackupSession;
|
||||
}
|
||||
|
||||
export interface ICurve25519AuthData {
|
||||
public_key: string;
|
||||
private_key_salt?: string;
|
||||
private_key_iterations?: number;
|
||||
private_key_bits?: number;
|
||||
}
|
||||
// Export for backward compatibility
|
||||
export type {
|
||||
Curve25519AuthData as ICurve25519AuthData,
|
||||
Aes256AuthData as IAes256AuthData,
|
||||
KeyBackupInfo as IKeyBackupInfo,
|
||||
} from "../crypto-api/keybackup";
|
||||
|
||||
export interface IAes256AuthData {
|
||||
iv: string;
|
||||
mac: string;
|
||||
private_key_salt?: string;
|
||||
private_key_iterations?: number;
|
||||
}
|
||||
|
||||
export interface IKeyBackupInfo {
|
||||
algorithm: string;
|
||||
auth_data: ISigned & (ICurve25519AuthData | IAes256AuthData);
|
||||
count?: number;
|
||||
etag?: string;
|
||||
version?: string; // number contained within
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
export interface IKeyBackupPrepareOpts {
|
||||
|
||||
@@ -24,6 +24,16 @@ import { EventType } from "../../../@types/event";
|
||||
import { VerificationBase } from "../Base";
|
||||
import { VerificationMethod } from "../../index";
|
||||
import { TypedEventEmitter } from "../../../models/typed-event-emitter";
|
||||
import {
|
||||
canAcceptVerificationRequest,
|
||||
VerificationPhase as Phase,
|
||||
VerificationRequest as IVerificationRequest,
|
||||
VerificationRequestEvent,
|
||||
VerificationRequestEventHandlerMap,
|
||||
} from "../../../crypto-api/verification";
|
||||
|
||||
// backwards-compatibility exports
|
||||
export { VerificationPhase as Phase, VerificationRequestEvent } from "../../../crypto-api/verification";
|
||||
|
||||
// How long after the event's timestamp that the request times out
|
||||
const TIMEOUT_FROM_EVENT_TS = 10 * 60 * 1000; // 10 minutes
|
||||
@@ -44,15 +54,6 @@ export const CANCEL_TYPE = EVENT_PREFIX + "cancel";
|
||||
export const DONE_TYPE = EVENT_PREFIX + "done";
|
||||
export const READY_TYPE = EVENT_PREFIX + "ready";
|
||||
|
||||
export enum Phase {
|
||||
Unsent = 1,
|
||||
Requested,
|
||||
Ready,
|
||||
Started,
|
||||
Cancelled,
|
||||
Done,
|
||||
}
|
||||
|
||||
// Legacy export fields
|
||||
export const PHASE_UNSENT = Phase.Unsent;
|
||||
export const PHASE_REQUESTED = Phase.Requested;
|
||||
@@ -71,26 +72,17 @@ interface ITransition {
|
||||
event?: MatrixEvent;
|
||||
}
|
||||
|
||||
export enum VerificationRequestEvent {
|
||||
Change = "change",
|
||||
}
|
||||
|
||||
type EventHandlerMap = {
|
||||
/**
|
||||
* Fires whenever the state of the request object has changed.
|
||||
*/
|
||||
[VerificationRequestEvent.Change]: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* State machine for verification requests.
|
||||
* Things that differ based on what channel is used to
|
||||
* send and receive verification events are put in `InRoomChannel` or `ToDeviceChannel`.
|
||||
*
|
||||
* @deprecated Avoid direct references: instead prefer {@link Crypto.VerificationRequest}.
|
||||
*/
|
||||
export class VerificationRequest<C extends IVerificationChannel = IVerificationChannel> extends TypedEventEmitter<
|
||||
VerificationRequestEvent,
|
||||
EventHandlerMap
|
||||
> {
|
||||
export class VerificationRequest<C extends IVerificationChannel = IVerificationChannel>
|
||||
extends TypedEventEmitter<VerificationRequestEvent, VerificationRequestEventHandlerMap>
|
||||
implements IVerificationRequest
|
||||
{
|
||||
private eventsByUs = new Map<string, MatrixEvent>();
|
||||
private eventsByThem = new Map<string, MatrixEvent>();
|
||||
private _observeOnly = false;
|
||||
@@ -257,7 +249,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
|
||||
}
|
||||
|
||||
public get canAccept(): boolean {
|
||||
return this.phase < PHASE_READY && !this._accepting && !this._declining;
|
||||
return canAcceptVerificationRequest(this);
|
||||
}
|
||||
|
||||
public get accepting(): boolean {
|
||||
|
||||
@@ -2008,6 +2008,11 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
const poll = new Poll(event, this.client, this);
|
||||
this.polls.set(event.getId()!, poll);
|
||||
this.emit(PollEvent.New, poll);
|
||||
|
||||
// remove the poll when redacted
|
||||
event.once(MatrixEventEvent.BeforeRedaction, (redactedEvent: MatrixEvent) => {
|
||||
this.polls.delete(redactedEvent.getId()!);
|
||||
});
|
||||
} catch {}
|
||||
// poll creation can fail for malformed poll start events
|
||||
return;
|
||||
|
||||
@@ -21,6 +21,7 @@ import { logger } from "../logger";
|
||||
import { RUST_SDK_STORE_PREFIX } from "./constants";
|
||||
import { IHttpOpts, MatrixHttpApi } from "../http-api";
|
||||
import { ServerSideSecretStorage } from "../secret-storage";
|
||||
import { ICryptoCallbacks } from "../crypto";
|
||||
|
||||
/**
|
||||
* Create a new `RustCrypto` implementation
|
||||
@@ -30,12 +31,14 @@ import { ServerSideSecretStorage } from "../secret-storage";
|
||||
* @param userId - The local user's User ID.
|
||||
* @param deviceId - The local user's Device ID.
|
||||
* @param secretStorage - Interface to server-side secret storage.
|
||||
* @param cryptoCallbacks - Crypto callbacks provided by the application
|
||||
*/
|
||||
export async function initRustCrypto(
|
||||
http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
secretStorage: ServerSideSecretStorage,
|
||||
cryptoCallbacks: ICryptoCallbacks,
|
||||
): Promise<RustCrypto> {
|
||||
// initialise the rust matrix-sdk-crypto-js, if it hasn't already been done
|
||||
await RustSdkCryptoJs.initAsync();
|
||||
@@ -49,11 +52,22 @@ export async function initRustCrypto(
|
||||
|
||||
// TODO: use the pickle key for the passphrase
|
||||
const olmMachine = await RustSdkCryptoJs.OlmMachine.initialize(u, d, RUST_SDK_STORE_PREFIX, "test pass");
|
||||
const rustCrypto = new RustCrypto(olmMachine, http, userId, deviceId, secretStorage);
|
||||
const rustCrypto = new RustCrypto(olmMachine, http, userId, deviceId, secretStorage, cryptoCallbacks);
|
||||
await olmMachine.registerRoomKeyUpdatedCallback((sessions: RustSdkCryptoJs.RoomKeyInfo[]) =>
|
||||
rustCrypto.onRoomKeysUpdated(sessions),
|
||||
);
|
||||
|
||||
// Tell the OlmMachine to think about its outgoing requests before we hand control back to the application.
|
||||
//
|
||||
// This is primarily a fudge to get it to correctly populate the `users_for_key_query` list, so that future
|
||||
// calls to getIdentity (etc) block until the key queries are performed.
|
||||
//
|
||||
// Note that we don't actually need to *make* any requests here; it is sufficient to tell the Rust side to think
|
||||
// about them.
|
||||
//
|
||||
// XXX: find a less hacky way to do this.
|
||||
await olmMachine.outgoingRequests();
|
||||
|
||||
logger.info("Completed rust crypto-sdk setup");
|
||||
return rustCrypto;
|
||||
}
|
||||
|
||||
+148
-16
@@ -32,18 +32,25 @@ import { KeyClaimManager } from "./KeyClaimManager";
|
||||
import { MapWithDefault } from "../utils";
|
||||
import {
|
||||
BootstrapCrossSigningOpts,
|
||||
CrossSigningKey,
|
||||
CrossSigningStatus,
|
||||
DeviceVerificationStatus,
|
||||
GeneratedSecretStorageKey,
|
||||
ImportRoomKeyProgressData,
|
||||
ImportRoomKeysOpts,
|
||||
VerificationRequest,
|
||||
CreateSecretStorageOpts,
|
||||
CryptoCallbacks,
|
||||
} from "../crypto-api";
|
||||
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter";
|
||||
import { IDownloadKeyResult, IQueryKeysRequest } from "../client";
|
||||
import { Device, DeviceMap } from "../models/device";
|
||||
import { ServerSideSecretStorage } from "../secret-storage";
|
||||
import { CrossSigningKey } from "../crypto/api";
|
||||
import { AddSecretStorageKeyOpts, SECRET_STORAGE_ALGORITHM_V1_AES, ServerSideSecretStorage } from "../secret-storage";
|
||||
import { CrossSigningIdentity } from "./CrossSigningIdentity";
|
||||
import { secretStorageContainsCrossSigningKeys } from "./secret-storage";
|
||||
import { keyFromPassphrase } from "../crypto/key_passphrase";
|
||||
import { encodeRecoveryKey } from "../crypto/recoverykey";
|
||||
import { crypto } from "../crypto/crypto";
|
||||
|
||||
/**
|
||||
* An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto.
|
||||
@@ -85,6 +92,9 @@ export class RustCrypto implements CryptoBackend {
|
||||
|
||||
/** Interface to server-side secret storage */
|
||||
private readonly secretStorage: ServerSideSecretStorage,
|
||||
|
||||
/** Crypto callbacks provided by the application */
|
||||
private readonly cryptoCallbacks: CryptoCallbacks,
|
||||
) {
|
||||
this.outgoingRequestProcessor = new OutgoingRequestProcessor(olmMachine, http);
|
||||
this.keyClaimManager = new KeyClaimManager(olmMachine, this.outgoingRequestProcessor);
|
||||
@@ -161,18 +171,6 @@ export class RustCrypto implements CryptoBackend {
|
||||
return new UserTrustLevel(false, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a DM verification request that is already in progress for the given room id
|
||||
*
|
||||
* @param roomId - the room to use for verification
|
||||
*
|
||||
* @returns the VerificationRequest that is in progress, if any
|
||||
*/
|
||||
public findVerificationRequestDMInProgress(roomId: string): undefined {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cross signing information for a given user.
|
||||
*
|
||||
@@ -195,9 +193,12 @@ export class RustCrypto implements CryptoBackend {
|
||||
|
||||
public globalBlacklistUnverifiedDevices = false;
|
||||
|
||||
/**
|
||||
* Implementation of {@link CryptoApi.userHasCrossSigningKeys}.
|
||||
*/
|
||||
public async userHasCrossSigningKeys(): Promise<boolean> {
|
||||
// TODO
|
||||
return false;
|
||||
const userIdentity = await this.olmMachine.getIdentity(new RustSdkCryptoJs.UserId(this.userId));
|
||||
return userIdentity !== undefined;
|
||||
}
|
||||
|
||||
public prepareToEncrypt(room: Room): void {
|
||||
@@ -379,6 +380,49 @@ export class RustCrypto implements CryptoBackend {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link CryptoApi#bootstrapSecretStorage}
|
||||
*/
|
||||
public async bootstrapSecretStorage({
|
||||
createSecretStorageKey,
|
||||
setupNewSecretStorage,
|
||||
}: CreateSecretStorageOpts = {}): Promise<void> {
|
||||
// If createSecretStorageKey is not set, we stop
|
||||
if (!createSecretStorageKey) return;
|
||||
|
||||
// See if we already have an AES secret-storage key.
|
||||
const secretStorageKeyTuple = await this.secretStorage.getKey();
|
||||
|
||||
if (secretStorageKeyTuple) {
|
||||
const [, keyInfo] = secretStorageKeyTuple;
|
||||
|
||||
// If an AES Key is already stored in the secret storage and setupNewSecretStorage is not set
|
||||
// we don't want to create a new key
|
||||
if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES && !setupNewSecretStorage) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const recoveryKey = await createSecretStorageKey();
|
||||
|
||||
// keyInfo is required to continue
|
||||
if (!recoveryKey.keyInfo) {
|
||||
throw new Error("missing keyInfo field in the secret storage key created by createSecretStorageKey");
|
||||
}
|
||||
|
||||
const secretStorageKeyObject = await this.secretStorage.addKey(
|
||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
recoveryKey.keyInfo,
|
||||
);
|
||||
await this.secretStorage.setDefaultKeyId(secretStorageKeyObject.keyId);
|
||||
|
||||
this.cryptoCallbacks.cacheSecretStorageKey?.(
|
||||
secretStorageKeyObject.keyId,
|
||||
secretStorageKeyObject.keyInfo,
|
||||
recoveryKey.privateKey,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link CryptoApi#getCrossSigningStatus}
|
||||
*/
|
||||
@@ -405,6 +449,94 @@ export class RustCrypto implements CryptoBackend {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link CryptoApi#createRecoveryKeyFromPassphrase}
|
||||
*/
|
||||
public async createRecoveryKeyFromPassphrase(password?: string): Promise<GeneratedSecretStorageKey> {
|
||||
let key: Uint8Array;
|
||||
|
||||
const keyInfo: AddSecretStorageKeyOpts = {};
|
||||
if (password) {
|
||||
// Generate the key from the passphrase
|
||||
const derivation = await keyFromPassphrase(password);
|
||||
keyInfo.passphrase = {
|
||||
algorithm: "m.pbkdf2",
|
||||
iterations: derivation.iterations,
|
||||
salt: derivation.salt,
|
||||
};
|
||||
key = derivation.key;
|
||||
} else {
|
||||
// Using the navigator crypto API to generate the private key
|
||||
key = new Uint8Array(32);
|
||||
crypto.getRandomValues(key);
|
||||
}
|
||||
|
||||
const encodedPrivateKey = encodeRecoveryKey(key);
|
||||
return {
|
||||
keyInfo,
|
||||
encodedPrivateKey,
|
||||
privateKey: key,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns to-device verification requests that are already in progress for the given user id.
|
||||
*
|
||||
* Implementation of {@link CryptoApi#getVerificationRequestsToDeviceInProgress}
|
||||
*
|
||||
* @param userId - the ID of the user to query
|
||||
*
|
||||
* @returns the VerificationRequests that are in progress
|
||||
*/
|
||||
public getVerificationRequestsToDeviceInProgress(userId: string): VerificationRequest[] {
|
||||
// TODO
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a DM verification request that is already in progress for the given room id
|
||||
*
|
||||
* Implementation of {@link CryptoApi#findVerificationRequestDMInProgress}
|
||||
*
|
||||
* @param roomId - the room to use for verification
|
||||
*
|
||||
* @returns the VerificationRequest that is in progress, if any
|
||||
*
|
||||
*/
|
||||
public findVerificationRequestDMInProgress(roomId: string): undefined {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a verification request to our other devices.
|
||||
*
|
||||
* If a verification is already in flight, returns it. Otherwise, initiates a new one.
|
||||
*
|
||||
* Implementation of {@link CryptoApi#requestOwnUserVerification}.
|
||||
*
|
||||
* @returns a VerificationRequest when the request has been sent to the other party.
|
||||
*/
|
||||
public requestOwnUserVerification(): Promise<VerificationRequest> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Request an interactive verification with the given device.
|
||||
*
|
||||
* If a verification is already in flight, returns it. Otherwise, initiates a new one.
|
||||
*
|
||||
* Implementation of {@link CryptoApi#requestDeviceVerification }.
|
||||
*
|
||||
* @param userId - ID of the owner of the device to verify
|
||||
* @param deviceId - ID of the device to verify
|
||||
*
|
||||
* @returns a VerificationRequest when the request has been sent to the other party.
|
||||
*/
|
||||
public requestDeviceVerification(userId: string, deviceId: string): Promise<VerificationRequest> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SyncCryptoCallbacks implementation
|
||||
|
||||
@@ -338,12 +338,16 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
|
||||
/**
|
||||
* Clear the entire database. This should be used when logging out of a client
|
||||
* to prevent mixing data between accounts.
|
||||
* to prevent mixing data between accounts. Closes the database.
|
||||
* @returns Resolved when the database is cleared.
|
||||
*/
|
||||
public clearDatabase(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
logger.log(`Removing indexeddb instance: ${this.dbName}`);
|
||||
|
||||
// Close the database first to avoid firing unexpected close events
|
||||
this.db?.close();
|
||||
|
||||
const req = this.indexedDB.deleteDatabase(this.dbName);
|
||||
|
||||
req.onblocked = (): void => {
|
||||
|
||||
@@ -334,7 +334,19 @@ export class MediaHandler extends TypedEventEmitter<
|
||||
this.emit(MediaHandlerEvent.LocalStreamsChanged);
|
||||
|
||||
if (this.localUserMediaStream === mediaStream) {
|
||||
// if we have this stream cahced, remove it, because we've stopped it
|
||||
this.localUserMediaStream = undefined;
|
||||
} else {
|
||||
// If it's not the same stream. remove any tracks from the cached stream that
|
||||
// we have just stopped, and if we do stop any, call the same method on the
|
||||
// cached stream too in order to stop all its tracks (in case they are different)
|
||||
// and un-cache it.
|
||||
for (const track of mediaStream.getTracks()) {
|
||||
if (this.localUserMediaStream?.getTrackById(track.id)) {
|
||||
this.stopUserMediaStream(this.localUserMediaStream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1790,9 +1790,9 @@
|
||||
integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==
|
||||
|
||||
"@types/node@18":
|
||||
version "18.16.16"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.16.tgz#3b64862856c7874ccf7439e6bab872d245c86d8e"
|
||||
integrity sha512-NpaM49IGQQAUlBhHMF82QH80J08os4ZmyF9MkpCzWAGuOHqE4gTEbhzd7L3l5LmWuZ6E0OiC1FweQ4tsiW35+g==
|
||||
version "18.16.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.18.tgz#85da09bafb66d4bc14f7c899185336d0c1736390"
|
||||
integrity sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.1"
|
||||
@@ -1830,9 +1830,9 @@
|
||||
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
|
||||
|
||||
"@types/uuid@9":
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.1.tgz#98586dc36aee8dacc98cc396dbca8d0429647aa6"
|
||||
integrity sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.2.tgz#ede1d1b1e451548d44919dc226253e32a6952c4b"
|
||||
integrity sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==
|
||||
|
||||
"@types/webidl-conversions@*":
|
||||
version "7.0.0"
|
||||
@@ -1852,14 +1852,14 @@
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.45.0":
|
||||
version "5.59.8"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.8.tgz#1e7a3e5318ece22251dfbc5c9c6feeb4793cc509"
|
||||
integrity sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==
|
||||
version "5.59.11"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz#8d466aa21abea4c3f37129997b198d141f09e76f"
|
||||
integrity sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.4.0"
|
||||
"@typescript-eslint/scope-manager" "5.59.8"
|
||||
"@typescript-eslint/type-utils" "5.59.8"
|
||||
"@typescript-eslint/utils" "5.59.8"
|
||||
"@typescript-eslint/scope-manager" "5.59.11"
|
||||
"@typescript-eslint/type-utils" "5.59.11"
|
||||
"@typescript-eslint/utils" "5.59.11"
|
||||
debug "^4.3.4"
|
||||
grapheme-splitter "^1.0.4"
|
||||
ignore "^5.2.0"
|
||||
@@ -1868,15 +1868,23 @@
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/parser@^5.45.0":
|
||||
version "5.59.8"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.8.tgz#60cbb00671d86cf746044ab797900b1448188567"
|
||||
integrity sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw==
|
||||
version "5.59.11"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.11.tgz#af7d4b7110e3068ce0b97550736de455e4250103"
|
||||
integrity sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.59.8"
|
||||
"@typescript-eslint/types" "5.59.8"
|
||||
"@typescript-eslint/typescript-estree" "5.59.8"
|
||||
"@typescript-eslint/scope-manager" "5.59.11"
|
||||
"@typescript-eslint/types" "5.59.11"
|
||||
"@typescript-eslint/typescript-estree" "5.59.11"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.59.11":
|
||||
version "5.59.11"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz#5d131a67a19189c42598af9fb2ea1165252001ce"
|
||||
integrity sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.59.11"
|
||||
"@typescript-eslint/visitor-keys" "5.59.11"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.59.6":
|
||||
version "5.59.6"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz#d43a3687aa4433868527cfe797eb267c6be35f19"
|
||||
@@ -1885,33 +1893,38 @@
|
||||
"@typescript-eslint/types" "5.59.6"
|
||||
"@typescript-eslint/visitor-keys" "5.59.6"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.59.8":
|
||||
version "5.59.8"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz#ff4ad4fec6433647b817c4a7d4b4165d18ea2fa8"
|
||||
integrity sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==
|
||||
"@typescript-eslint/type-utils@5.59.11":
|
||||
version "5.59.11"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz#5eb67121808a84cb57d65a15f48f5bdda25f2346"
|
||||
integrity sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.59.8"
|
||||
"@typescript-eslint/visitor-keys" "5.59.8"
|
||||
|
||||
"@typescript-eslint/type-utils@5.59.8":
|
||||
version "5.59.8"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.8.tgz#aa6c029a9d7706d26bbd25eb4666398781df6ea2"
|
||||
integrity sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "5.59.8"
|
||||
"@typescript-eslint/utils" "5.59.8"
|
||||
"@typescript-eslint/typescript-estree" "5.59.11"
|
||||
"@typescript-eslint/utils" "5.59.11"
|
||||
debug "^4.3.4"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/types@5.59.11":
|
||||
version "5.59.11"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.11.tgz#1a9018fe3c565ba6969561f2a49f330cf1fe8db1"
|
||||
integrity sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==
|
||||
|
||||
"@typescript-eslint/types@5.59.6":
|
||||
version "5.59.6"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.6.tgz#5a6557a772af044afe890d77c6a07e8c23c2460b"
|
||||
integrity sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==
|
||||
|
||||
"@typescript-eslint/types@5.59.8":
|
||||
version "5.59.8"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.8.tgz#212e54414733618f5d0fd50b2da2717f630aebf8"
|
||||
integrity sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==
|
||||
"@typescript-eslint/typescript-estree@5.59.11":
|
||||
version "5.59.11"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz#b2caaa31725e17c33970c1197bcd54e3c5f42b9f"
|
||||
integrity sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.59.11"
|
||||
"@typescript-eslint/visitor-keys" "5.59.11"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.59.6":
|
||||
version "5.59.6"
|
||||
@@ -1926,30 +1939,17 @@
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.59.8":
|
||||
version "5.59.8"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz#801a7b1766481629481b3b0878148bd7a1f345d7"
|
||||
integrity sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.59.8"
|
||||
"@typescript-eslint/visitor-keys" "5.59.8"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/utils@5.59.8":
|
||||
version "5.59.8"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.8.tgz#34d129f35a2134c67fdaf024941e8f96050dca2b"
|
||||
integrity sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==
|
||||
"@typescript-eslint/utils@5.59.11":
|
||||
version "5.59.11"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.11.tgz#9dbff49dc80bfdd9289f9f33548f2e8db3c59ba1"
|
||||
integrity sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@types/json-schema" "^7.0.9"
|
||||
"@types/semver" "^7.3.12"
|
||||
"@typescript-eslint/scope-manager" "5.59.8"
|
||||
"@typescript-eslint/types" "5.59.8"
|
||||
"@typescript-eslint/typescript-estree" "5.59.8"
|
||||
"@typescript-eslint/scope-manager" "5.59.11"
|
||||
"@typescript-eslint/types" "5.59.11"
|
||||
"@typescript-eslint/typescript-estree" "5.59.11"
|
||||
eslint-scope "^5.1.1"
|
||||
semver "^7.3.7"
|
||||
|
||||
@@ -1967,6 +1967,14 @@
|
||||
eslint-scope "^5.1.1"
|
||||
semver "^7.3.7"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.59.11":
|
||||
version "5.59.11"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz#dca561ddad169dc27d62396d64f45b2d2c3ecc56"
|
||||
integrity sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.59.11"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.59.6":
|
||||
version "5.59.6"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz#673fccabf28943847d0c8e9e8d008e3ada7be6bb"
|
||||
@@ -1975,14 +1983,6 @@
|
||||
"@typescript-eslint/types" "5.59.6"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.59.8":
|
||||
version "5.59.8"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz#aa6a7ef862add919401470c09e1609392ef3cc40"
|
||||
integrity sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.59.8"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
JSONStream@^1.0.3:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
||||
@@ -3623,10 +3623,10 @@ eslint-plugin-jest@^27.1.6:
|
||||
dependencies:
|
||||
"@typescript-eslint/utils" "^5.10.0"
|
||||
|
||||
eslint-plugin-jsdoc@^45.0.0:
|
||||
version "45.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-45.0.0.tgz#6be84e4842a7138cc571a907ea9c31c42eaac5c0"
|
||||
integrity sha512-l2+Jcs/Ps7oFA+SWY+0sweU/e5LgricnEl6EsDlyRTF5y0+NWL1y9Qwz9PHwHAxtdJq6lxPjEQWmYLMkvhzD4g==
|
||||
eslint-plugin-jsdoc@^46.0.0:
|
||||
version "46.2.6"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.2.6.tgz#f25590d371859f20691d65b5dcd4cbe370d65564"
|
||||
integrity sha512-zIaK3zbSrKuH12bP+SPybPgcHSM6MFzh3HFeaODzmsF1N8C1l8dzJ22cW1aq4g0+nayU1VMjmNf7hg0dpShLrA==
|
||||
dependencies:
|
||||
"@es-joy/jsdoccomment" "~0.39.4"
|
||||
are-docs-informative "^0.0.2"
|
||||
@@ -3634,6 +3634,7 @@ eslint-plugin-jsdoc@^45.0.0:
|
||||
debug "^4.3.4"
|
||||
escape-string-regexp "^4.0.0"
|
||||
esquery "^1.5.0"
|
||||
is-builtin-module "^3.2.1"
|
||||
semver "^7.5.1"
|
||||
spdx-expression-parse "^3.0.1"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user