first commit
This commit is contained in:
108
node_modules/matrix-bot-sdk/lib/e2ee/CryptoClient.d.ts
generated
vendored
Normal file
108
node_modules/matrix-bot-sdk/lib/e2ee/CryptoClient.d.ts
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
/// <reference types="node" />
|
||||
import { MatrixClient } from "../MatrixClient";
|
||||
import { IMegolmEncrypted, IOlmEncrypted, IToDeviceMessage, OTKAlgorithm, OTKCounts, Signatures } from "../models/Crypto";
|
||||
import { EncryptedRoomEvent } from "../models/events/EncryptedRoomEvent";
|
||||
import { RoomEvent } from "../models/events/RoomEvent";
|
||||
import { EncryptedFile } from "../models/events/MessageEvent";
|
||||
/**
|
||||
* Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly
|
||||
* rather than creating one manually.
|
||||
* @category Encryption
|
||||
*/
|
||||
export declare class CryptoClient {
|
||||
private client;
|
||||
private ready;
|
||||
private deviceId;
|
||||
private deviceEd25519;
|
||||
private deviceCurve25519;
|
||||
private roomTracker;
|
||||
private engine;
|
||||
constructor(client: MatrixClient);
|
||||
private get storage();
|
||||
/**
|
||||
* The device ID for the MatrixClient.
|
||||
*/
|
||||
get clientDeviceId(): string;
|
||||
/**
|
||||
* Whether or not the crypto client is ready to be used. If not ready, prepare() should be called.
|
||||
* @see prepare
|
||||
*/
|
||||
get isReady(): boolean;
|
||||
/**
|
||||
* Prepares the crypto client for usage.
|
||||
* @param {string[]} roomIds The room IDs the MatrixClient is joined to.
|
||||
*/
|
||||
prepare(roomIds: string[]): Promise<void>;
|
||||
/**
|
||||
* Handles a room event.
|
||||
* @internal
|
||||
* @param roomId The room ID.
|
||||
* @param event The event.
|
||||
*/
|
||||
onRoomEvent(roomId: string, event: any): Promise<void>;
|
||||
/**
|
||||
* Handles a room join.
|
||||
* @internal
|
||||
* @param roomId The room ID.
|
||||
*/
|
||||
onRoomJoin(roomId: string): Promise<void>;
|
||||
/**
|
||||
* Checks if a room is encrypted.
|
||||
* @param {string} roomId The room ID to check.
|
||||
* @returns {Promise<boolean>} Resolves to true if encrypted, false otherwise.
|
||||
*/
|
||||
isRoomEncrypted(roomId: string): Promise<boolean>;
|
||||
/**
|
||||
* Updates the client's sync-related data.
|
||||
* @param {Array.<IToDeviceMessage<IOlmEncrypted>>} toDeviceMessages The to-device messages received.
|
||||
* @param {OTKCounts} otkCounts The current OTK counts.
|
||||
* @param {OTKAlgorithm[]} unusedFallbackKeyAlgs The unused fallback key algorithms.
|
||||
* @param {string[]} changedDeviceLists The user IDs which had device list changes.
|
||||
* @param {string[]} leftDeviceLists The user IDs which the server believes we no longer need to track.
|
||||
* @returns {Promise<void>} Resolves when complete.
|
||||
*/
|
||||
updateSyncData(toDeviceMessages: IToDeviceMessage<IOlmEncrypted>[], otkCounts: OTKCounts, unusedFallbackKeyAlgs: OTKAlgorithm[], changedDeviceLists: string[], leftDeviceLists: string[]): Promise<void>;
|
||||
/**
|
||||
* Signs an object using the device keys.
|
||||
* @param {object} obj The object to sign.
|
||||
* @returns {Promise<Signatures>} The signatures for the object.
|
||||
*/
|
||||
sign(obj: object): Promise<Signatures>;
|
||||
/**
|
||||
* Encrypts the details of a room event, returning an encrypted payload to be sent in an
|
||||
* `m.room.encrypted` event to the room. If needed, this function will send decryption keys
|
||||
* to the appropriate devices in the room (this happens when the Megolm session rotates or
|
||||
* gets created).
|
||||
* @param {string} roomId The room ID to encrypt within. If the room is not encrypted, an
|
||||
* error is thrown.
|
||||
* @param {string} eventType The event type being encrypted.
|
||||
* @param {any} content The event content being encrypted.
|
||||
* @returns {Promise<IMegolmEncrypted>} Resolves to the encrypted content for an `m.room.encrypted` event.
|
||||
*/
|
||||
encryptRoomEvent(roomId: string, eventType: string, content: any): Promise<IMegolmEncrypted>;
|
||||
/**
|
||||
* Decrypts a room event. Currently only supports Megolm-encrypted events (default for this SDK).
|
||||
* @param {EncryptedRoomEvent} event The encrypted event.
|
||||
* @param {string} roomId The room ID where the event was sent.
|
||||
* @returns {Promise<RoomEvent<unknown>>} Resolves to a decrypted room event, or rejects/throws with
|
||||
* an error if the event is undecryptable.
|
||||
*/
|
||||
decryptRoomEvent(event: EncryptedRoomEvent, roomId: string): Promise<RoomEvent<unknown>>;
|
||||
/**
|
||||
* Encrypts a file for uploading in a room, returning the encrypted data and information
|
||||
* to include in a message event (except media URL) for sending.
|
||||
* @param {Buffer} file The file to encrypt.
|
||||
* @returns {{buffer: Buffer, file: Omit<EncryptedFile, "url">}} Resolves to the encrypted
|
||||
* contents and file information.
|
||||
*/
|
||||
encryptMedia(file: Buffer): Promise<{
|
||||
buffer: Buffer;
|
||||
file: Omit<EncryptedFile, "url">;
|
||||
}>;
|
||||
/**
|
||||
* Decrypts a previously-uploaded encrypted file, validating the fields along the way.
|
||||
* @param {EncryptedFile} file The file to decrypt.
|
||||
* @returns {Promise<Buffer>} Resolves to the decrypted file contents.
|
||||
*/
|
||||
decryptMedia(file: EncryptedFile): Promise<Buffer>;
|
||||
}
|
273
node_modules/matrix-bot-sdk/lib/e2ee/CryptoClient.js
generated
vendored
Normal file
273
node_modules/matrix-bot-sdk/lib/e2ee/CryptoClient.js
generated
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CryptoClient = void 0;
|
||||
const matrix_sdk_crypto_nodejs_1 = require("@matrix-org/matrix-sdk-crypto-nodejs");
|
||||
const LogService_1 = require("../logging/LogService");
|
||||
const decorators_1 = require("./decorators");
|
||||
const RoomTracker_1 = require("./RoomTracker");
|
||||
const EncryptedRoomEvent_1 = require("../models/events/EncryptedRoomEvent");
|
||||
const RoomEvent_1 = require("../models/events/RoomEvent");
|
||||
const RustEngine_1 = require("./RustEngine");
|
||||
const MembershipEvent_1 = require("../models/events/MembershipEvent");
|
||||
/**
|
||||
* Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly
|
||||
* rather than creating one manually.
|
||||
* @category Encryption
|
||||
*/
|
||||
class CryptoClient {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
this.ready = false;
|
||||
this.roomTracker = new RoomTracker_1.RoomTracker(this.client);
|
||||
}
|
||||
get storage() {
|
||||
return this.client.cryptoStore;
|
||||
}
|
||||
/**
|
||||
* The device ID for the MatrixClient.
|
||||
*/
|
||||
get clientDeviceId() {
|
||||
return this.deviceId;
|
||||
}
|
||||
/**
|
||||
* Whether or not the crypto client is ready to be used. If not ready, prepare() should be called.
|
||||
* @see prepare
|
||||
*/
|
||||
get isReady() {
|
||||
return this.ready;
|
||||
}
|
||||
/**
|
||||
* Prepares the crypto client for usage.
|
||||
* @param {string[]} roomIds The room IDs the MatrixClient is joined to.
|
||||
*/
|
||||
async prepare(roomIds) {
|
||||
await this.roomTracker.prepare(roomIds);
|
||||
if (this.ready)
|
||||
return; // stop re-preparing here
|
||||
const storedDeviceId = await this.client.cryptoStore.getDeviceId();
|
||||
if (storedDeviceId) {
|
||||
this.deviceId = storedDeviceId;
|
||||
}
|
||||
else {
|
||||
const deviceId = (await this.client.getWhoAmI())['device_id'];
|
||||
if (!deviceId) {
|
||||
throw new Error("Encryption not possible: server not revealing device ID");
|
||||
}
|
||||
this.deviceId = deviceId;
|
||||
await this.client.cryptoStore.setDeviceId(this.deviceId);
|
||||
}
|
||||
LogService_1.LogService.debug("CryptoClient", "Starting with device ID:", this.deviceId);
|
||||
const machine = await matrix_sdk_crypto_nodejs_1.OlmMachine.initialize(new matrix_sdk_crypto_nodejs_1.UserId(await this.client.getUserId()), new matrix_sdk_crypto_nodejs_1.DeviceId(this.deviceId), this.storage.storagePath, "", this.storage.storageType);
|
||||
this.engine = new RustEngine_1.RustEngine(machine, this.client);
|
||||
await this.engine.run();
|
||||
const identity = this.engine.machine.identityKeys;
|
||||
this.deviceCurve25519 = identity.curve25519.toBase64();
|
||||
this.deviceEd25519 = identity.ed25519.toBase64();
|
||||
this.ready = true;
|
||||
}
|
||||
/**
|
||||
* Handles a room event.
|
||||
* @internal
|
||||
* @param roomId The room ID.
|
||||
* @param event The event.
|
||||
*/
|
||||
async onRoomEvent(roomId, event) {
|
||||
await this.roomTracker.onRoomEvent(roomId, event);
|
||||
if (typeof event['state_key'] !== 'string')
|
||||
return;
|
||||
if (event['type'] === 'm.room.member') {
|
||||
const membership = new MembershipEvent_1.MembershipEvent(event);
|
||||
if (membership.effectiveMembership !== 'join' && membership.effectiveMembership !== 'invite')
|
||||
return;
|
||||
await this.engine.addTrackedUsers([membership.membershipFor]);
|
||||
}
|
||||
else if (event['type'] === 'm.room.encryption') {
|
||||
const members = await this.client.getRoomMembers(roomId, null, ['join', 'invite']);
|
||||
await this.engine.addTrackedUsers(members.map(e => e.membershipFor));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handles a room join.
|
||||
* @internal
|
||||
* @param roomId The room ID.
|
||||
*/
|
||||
async onRoomJoin(roomId) {
|
||||
await this.roomTracker.onRoomJoin(roomId);
|
||||
if (await this.isRoomEncrypted(roomId)) {
|
||||
const members = await this.client.getRoomMembers(roomId, null, ['join', 'invite']);
|
||||
await this.engine.addTrackedUsers(members.map(e => e.membershipFor));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks if a room is encrypted.
|
||||
* @param {string} roomId The room ID to check.
|
||||
* @returns {Promise<boolean>} Resolves to true if encrypted, false otherwise.
|
||||
*/
|
||||
async isRoomEncrypted(roomId) {
|
||||
const config = await this.roomTracker.getRoomCryptoConfig(roomId);
|
||||
return !!config?.algorithm;
|
||||
}
|
||||
/**
|
||||
* Updates the client's sync-related data.
|
||||
* @param {Array.<IToDeviceMessage<IOlmEncrypted>>} toDeviceMessages The to-device messages received.
|
||||
* @param {OTKCounts} otkCounts The current OTK counts.
|
||||
* @param {OTKAlgorithm[]} unusedFallbackKeyAlgs The unused fallback key algorithms.
|
||||
* @param {string[]} changedDeviceLists The user IDs which had device list changes.
|
||||
* @param {string[]} leftDeviceLists The user IDs which the server believes we no longer need to track.
|
||||
* @returns {Promise<void>} Resolves when complete.
|
||||
*/
|
||||
async updateSyncData(toDeviceMessages, otkCounts, unusedFallbackKeyAlgs, changedDeviceLists, leftDeviceLists) {
|
||||
const deviceMessages = JSON.stringify(toDeviceMessages);
|
||||
const deviceLists = new matrix_sdk_crypto_nodejs_1.DeviceLists(changedDeviceLists.map(u => new matrix_sdk_crypto_nodejs_1.UserId(u)), leftDeviceLists.map(u => new matrix_sdk_crypto_nodejs_1.UserId(u)));
|
||||
await this.engine.lock.acquire(RustEngine_1.SYNC_LOCK_NAME, async () => {
|
||||
const syncResp = await this.engine.machine.receiveSyncChanges(deviceMessages, deviceLists, otkCounts, unusedFallbackKeyAlgs);
|
||||
const decryptedToDeviceMessages = JSON.parse(syncResp);
|
||||
if (Array.isArray(decryptedToDeviceMessages)) {
|
||||
for (const msg of decryptedToDeviceMessages) {
|
||||
this.client.emit("to_device.decrypted", msg);
|
||||
}
|
||||
}
|
||||
await this.engine.run();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Signs an object using the device keys.
|
||||
* @param {object} obj The object to sign.
|
||||
* @returns {Promise<Signatures>} The signatures for the object.
|
||||
*/
|
||||
async sign(obj) {
|
||||
obj = JSON.parse(JSON.stringify(obj));
|
||||
const existingSignatures = obj['signatures'] || {};
|
||||
delete obj['signatures'];
|
||||
delete obj['unsigned'];
|
||||
const container = await this.engine.machine.sign(JSON.stringify(obj));
|
||||
const userSignature = container.get(new matrix_sdk_crypto_nodejs_1.UserId(await this.client.getUserId()));
|
||||
const sig = {
|
||||
[await this.client.getUserId()]: {},
|
||||
};
|
||||
for (const [key, maybeSignature] of Object.entries(userSignature)) {
|
||||
if (maybeSignature.isValid) {
|
||||
sig[await this.client.getUserId()][key] = maybeSignature.signature.toBase64();
|
||||
}
|
||||
}
|
||||
return {
|
||||
...sig,
|
||||
...existingSignatures,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Encrypts the details of a room event, returning an encrypted payload to be sent in an
|
||||
* `m.room.encrypted` event to the room. If needed, this function will send decryption keys
|
||||
* to the appropriate devices in the room (this happens when the Megolm session rotates or
|
||||
* gets created).
|
||||
* @param {string} roomId The room ID to encrypt within. If the room is not encrypted, an
|
||||
* error is thrown.
|
||||
* @param {string} eventType The event type being encrypted.
|
||||
* @param {any} content The event content being encrypted.
|
||||
* @returns {Promise<IMegolmEncrypted>} Resolves to the encrypted content for an `m.room.encrypted` event.
|
||||
*/
|
||||
async encryptRoomEvent(roomId, eventType, content) {
|
||||
if (!(await this.isRoomEncrypted(roomId))) {
|
||||
throw new Error("Room is not encrypted");
|
||||
}
|
||||
await this.engine.prepareEncrypt(roomId, await this.roomTracker.getRoomCryptoConfig(roomId));
|
||||
const encrypted = JSON.parse(await this.engine.machine.encryptRoomEvent(new matrix_sdk_crypto_nodejs_1.RoomId(roomId), eventType, JSON.stringify(content)));
|
||||
await this.engine.run();
|
||||
return encrypted;
|
||||
}
|
||||
/**
|
||||
* Decrypts a room event. Currently only supports Megolm-encrypted events (default for this SDK).
|
||||
* @param {EncryptedRoomEvent} event The encrypted event.
|
||||
* @param {string} roomId The room ID where the event was sent.
|
||||
* @returns {Promise<RoomEvent<unknown>>} Resolves to a decrypted room event, or rejects/throws with
|
||||
* an error if the event is undecryptable.
|
||||
*/
|
||||
async decryptRoomEvent(event, roomId) {
|
||||
const decrypted = await this.engine.machine.decryptRoomEvent(JSON.stringify(event.raw), new matrix_sdk_crypto_nodejs_1.RoomId(roomId));
|
||||
const clearEvent = JSON.parse(decrypted.event);
|
||||
return new RoomEvent_1.RoomEvent({
|
||||
...event.raw,
|
||||
type: clearEvent.type || "io.t2bot.unknown",
|
||||
content: (typeof (clearEvent.content) === 'object') ? clearEvent.content : {},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Encrypts a file for uploading in a room, returning the encrypted data and information
|
||||
* to include in a message event (except media URL) for sending.
|
||||
* @param {Buffer} file The file to encrypt.
|
||||
* @returns {{buffer: Buffer, file: Omit<EncryptedFile, "url">}} Resolves to the encrypted
|
||||
* contents and file information.
|
||||
*/
|
||||
async encryptMedia(file) {
|
||||
const encrypted = matrix_sdk_crypto_nodejs_1.Attachment.encrypt(file);
|
||||
const info = JSON.parse(encrypted.mediaEncryptionInfo);
|
||||
return {
|
||||
buffer: Buffer.from(encrypted.encryptedData),
|
||||
file: info,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Decrypts a previously-uploaded encrypted file, validating the fields along the way.
|
||||
* @param {EncryptedFile} file The file to decrypt.
|
||||
* @returns {Promise<Buffer>} Resolves to the decrypted file contents.
|
||||
*/
|
||||
async decryptMedia(file) {
|
||||
const contents = (await this.client.downloadContent(file.url)).data;
|
||||
const encrypted = new matrix_sdk_crypto_nodejs_1.EncryptedAttachment(contents, JSON.stringify(file));
|
||||
const decrypted = matrix_sdk_crypto_nodejs_1.Attachment.decrypt(encrypted);
|
||||
return Buffer.from(decrypted);
|
||||
}
|
||||
}
|
||||
exports.CryptoClient = CryptoClient;
|
||||
__decorate([
|
||||
(0, decorators_1.requiresReady)(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], CryptoClient.prototype, "isRoomEncrypted", null);
|
||||
__decorate([
|
||||
(0, decorators_1.requiresReady)(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Array, Object, Array, Array, Array]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], CryptoClient.prototype, "updateSyncData", null);
|
||||
__decorate([
|
||||
(0, decorators_1.requiresReady)(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], CryptoClient.prototype, "sign", null);
|
||||
__decorate([
|
||||
(0, decorators_1.requiresReady)(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, String, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], CryptoClient.prototype, "encryptRoomEvent", null);
|
||||
__decorate([
|
||||
(0, decorators_1.requiresReady)(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [EncryptedRoomEvent_1.EncryptedRoomEvent, String]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], CryptoClient.prototype, "decryptRoomEvent", null);
|
||||
__decorate([
|
||||
(0, decorators_1.requiresReady)(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Buffer]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], CryptoClient.prototype, "encryptMedia", null);
|
||||
__decorate([
|
||||
(0, decorators_1.requiresReady)(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], CryptoClient.prototype, "decryptMedia", null);
|
||||
//# sourceMappingURL=CryptoClient.js.map
|
1
node_modules/matrix-bot-sdk/lib/e2ee/CryptoClient.js.map
generated
vendored
Normal file
1
node_modules/matrix-bot-sdk/lib/e2ee/CryptoClient.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
33
node_modules/matrix-bot-sdk/lib/e2ee/DeviceTracker.d.ts
generated
vendored
Normal file
33
node_modules/matrix-bot-sdk/lib/e2ee/DeviceTracker.d.ts
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
import { MatrixClient } from "../MatrixClient";
|
||||
import { UserDevice } from "../models/Crypto";
|
||||
/**
|
||||
* Tracks user devices for encryption operations.
|
||||
* @category Encryption
|
||||
*/
|
||||
export declare class DeviceTracker {
|
||||
private client;
|
||||
private deviceListUpdates;
|
||||
constructor(client: MatrixClient);
|
||||
/**
|
||||
* Gets the device lists for the given user IDs. Outdated device lists will be updated before
|
||||
* returning.
|
||||
* @param {string[]} userIds The user IDs to get the device lists of.
|
||||
* @returns {Promise<Record<string, UserDevice[]>>} Resolves to a map of user ID to device list.
|
||||
* If a user has no devices, they may be excluded from the result or appear as an empty array.
|
||||
*/
|
||||
getDevicesFor(userIds: string[]): Promise<Record<string, UserDevice[]>>;
|
||||
/**
|
||||
* Flags multiple user's device lists as outdated, optionally queuing an immediate update.
|
||||
* @param {string} userIds The user IDs to flag the device lists of.
|
||||
* @param {boolean} resync True (default) to queue an immediate update, false otherwise.
|
||||
* @returns {Promise<void>} Resolves when the flagging has completed. Will wait for the resync
|
||||
* if requested too.
|
||||
*/
|
||||
flagUsersOutdated(userIds: string[], resync?: boolean): Promise<void>;
|
||||
/**
|
||||
* Updates multiple user's device lists regardless of outdated flag.
|
||||
* @param {string[]} userIds The user IDs to update.
|
||||
* @returns {Promise<void>} Resolves when complete.
|
||||
*/
|
||||
updateUsersDeviceLists(userIds: string[]): Promise<void>;
|
||||
}
|
133
node_modules/matrix-bot-sdk/lib/e2ee/DeviceTracker.js
generated
vendored
Normal file
133
node_modules/matrix-bot-sdk/lib/e2ee/DeviceTracker.js
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DeviceTracker = void 0;
|
||||
const LogService_1 = require("../logging/LogService");
|
||||
const Crypto_1 = require("../models/Crypto");
|
||||
/**
|
||||
* Tracks user devices for encryption operations.
|
||||
* @category Encryption
|
||||
*/
|
||||
class DeviceTracker {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
this.deviceListUpdates = {};
|
||||
}
|
||||
/**
|
||||
* Gets the device lists for the given user IDs. Outdated device lists will be updated before
|
||||
* returning.
|
||||
* @param {string[]} userIds The user IDs to get the device lists of.
|
||||
* @returns {Promise<Record<string, UserDevice[]>>} Resolves to a map of user ID to device list.
|
||||
* If a user has no devices, they may be excluded from the result or appear as an empty array.
|
||||
*/
|
||||
getDevicesFor(userIds) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const outdatedUserIds = [];
|
||||
for (const userId of userIds) {
|
||||
const isOutdated = yield this.client.cryptoStore.isUserOutdated(userId);
|
||||
if (isOutdated)
|
||||
outdatedUserIds.push(userId);
|
||||
}
|
||||
yield this.updateUsersDeviceLists(outdatedUserIds);
|
||||
const userDeviceMap = {};
|
||||
for (const userId of userIds) {
|
||||
userDeviceMap[userId] = yield this.client.cryptoStore.getActiveUserDevices(userId);
|
||||
}
|
||||
return userDeviceMap;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Flags multiple user's device lists as outdated, optionally queuing an immediate update.
|
||||
* @param {string} userIds The user IDs to flag the device lists of.
|
||||
* @param {boolean} resync True (default) to queue an immediate update, false otherwise.
|
||||
* @returns {Promise<void>} Resolves when the flagging has completed. Will wait for the resync
|
||||
* if requested too.
|
||||
*/
|
||||
flagUsersOutdated(userIds, resync = true) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.client.cryptoStore.flagUsersOutdated(userIds);
|
||||
if (resync) {
|
||||
yield this.updateUsersDeviceLists(userIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Updates multiple user's device lists regardless of outdated flag.
|
||||
* @param {string[]} userIds The user IDs to update.
|
||||
* @returns {Promise<void>} Resolves when complete.
|
||||
*/
|
||||
updateUsersDeviceLists(userIds) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// We wait for the lock, but still run through with our update just in case we are lagged.
|
||||
// This can happen if the server is slow to reply to device list queries, but a user is
|
||||
// changing information about their device a lot.
|
||||
const existingPromises = userIds.map(u => this.deviceListUpdates[u]).filter(p => !!p);
|
||||
if (existingPromises.length > 0) {
|
||||
yield Promise.all(existingPromises);
|
||||
}
|
||||
const promise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
||||
var _a, _b;
|
||||
try {
|
||||
const resp = yield this.client.getUserDevices(userIds);
|
||||
for (const userId of Object.keys(resp.device_keys)) {
|
||||
if (!userIds.includes(userId)) {
|
||||
LogService_1.LogService.warn("DeviceTracker", `Server returned unexpected user ID: ${userId} - ignoring user`);
|
||||
continue;
|
||||
}
|
||||
const validated = [];
|
||||
for (const deviceId of Object.keys(resp.device_keys[userId])) {
|
||||
const device = resp.device_keys[userId][deviceId];
|
||||
if (device.user_id !== userId || device.device_id !== deviceId) {
|
||||
LogService_1.LogService.warn("DeviceTracker", `Server appears to be lying about device lists: ${userId} ${deviceId} has unexpected device ${device.user_id} ${device.device_id} listed - ignoring device`);
|
||||
continue;
|
||||
}
|
||||
const ed25519 = device.keys[`${Crypto_1.DeviceKeyAlgorithm.Ed25519}:${deviceId}`];
|
||||
const curve25519 = device.keys[`${Crypto_1.DeviceKeyAlgorithm.Curve25519}:${deviceId}`];
|
||||
if (!ed25519 || !curve25519) {
|
||||
LogService_1.LogService.warn("DeviceTracker", `Device ${userId} ${deviceId} is missing either an Ed25519 or Curve25519 key - ignoring device`);
|
||||
continue;
|
||||
}
|
||||
const currentDevices = yield this.client.cryptoStore.getAllUserDevices(userId);
|
||||
const existingDevice = currentDevices.find(d => d.device_id === deviceId);
|
||||
if (existingDevice) {
|
||||
const existingEd25519 = existingDevice.keys[`${Crypto_1.DeviceKeyAlgorithm.Ed25519}:${deviceId}`];
|
||||
if (existingEd25519 !== ed25519) {
|
||||
LogService_1.LogService.warn("DeviceTracker", `Device ${userId} ${deviceId} appears compromised: Ed25519 key changed - ignoring device`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const signature = (_b = (_a = device.signatures) === null || _a === void 0 ? void 0 : _a[userId]) === null || _b === void 0 ? void 0 : _b[`${Crypto_1.DeviceKeyAlgorithm.Ed25519}:${deviceId}`];
|
||||
if (!signature) {
|
||||
LogService_1.LogService.warn("DeviceTracker", `Device ${userId} ${deviceId} is missing a signature - ignoring device`);
|
||||
continue;
|
||||
}
|
||||
const validSignature = yield this.client.crypto.verifySignature(device, ed25519, signature);
|
||||
if (!validSignature) {
|
||||
LogService_1.LogService.warn("DeviceTracker", `Device ${userId} ${deviceId} has an invalid signature - ignoring device`);
|
||||
continue;
|
||||
}
|
||||
validated.push(device);
|
||||
}
|
||||
yield this.client.cryptoStore.setActiveUserDevices(userId, validated);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
LogService_1.LogService.error("DeviceTracker", "Error updating device lists:", e);
|
||||
// return reject(e);
|
||||
}
|
||||
resolve();
|
||||
}));
|
||||
userIds.forEach(u => this.deviceListUpdates[u] = promise);
|
||||
yield promise;
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.DeviceTracker = DeviceTracker;
|
8
node_modules/matrix-bot-sdk/lib/e2ee/ICryptoRoomInformation.d.ts
generated
vendored
Normal file
8
node_modules/matrix-bot-sdk/lib/e2ee/ICryptoRoomInformation.d.ts
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import { EncryptionEventContent } from "../models/events/EncryptionEvent";
|
||||
/**
|
||||
* Information about a room for the purposes of crypto.
|
||||
* @category Encryption
|
||||
*/
|
||||
export interface ICryptoRoomInformation extends Partial<EncryptionEventContent> {
|
||||
historyVisibility?: string;
|
||||
}
|
3
node_modules/matrix-bot-sdk/lib/e2ee/ICryptoRoomInformation.js
generated
vendored
Normal file
3
node_modules/matrix-bot-sdk/lib/e2ee/ICryptoRoomInformation.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=ICryptoRoomInformation.js.map
|
1
node_modules/matrix-bot-sdk/lib/e2ee/ICryptoRoomInformation.js.map
generated
vendored
Normal file
1
node_modules/matrix-bot-sdk/lib/e2ee/ICryptoRoomInformation.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ICryptoRoomInformation.js","sourceRoot":"","sources":["../../src/e2ee/ICryptoRoomInformation.ts"],"names":[],"mappings":""}
|
13
node_modules/matrix-bot-sdk/lib/e2ee/InternalOlmMachineFactory.d.ts
generated
vendored
Normal file
13
node_modules/matrix-bot-sdk/lib/e2ee/InternalOlmMachineFactory.d.ts
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { OlmEngine, OlmMachine } from "@turt2live/matrix-sdk-crypto-nodejs";
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export declare class InternalOlmMachineFactory {
|
||||
private userId;
|
||||
private deviceId;
|
||||
private engine;
|
||||
private storagePath;
|
||||
static FACTORY_OVERRIDE: (userId: string, deviceId: string, engine: OlmEngine, storagePath: string) => OlmMachine;
|
||||
constructor(userId: string, deviceId: string, engine: OlmEngine, storagePath: string);
|
||||
build(): OlmMachine;
|
||||
}
|
22
node_modules/matrix-bot-sdk/lib/e2ee/InternalOlmMachineFactory.js
generated
vendored
Normal file
22
node_modules/matrix-bot-sdk/lib/e2ee/InternalOlmMachineFactory.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.InternalOlmMachineFactory = void 0;
|
||||
const matrix_sdk_crypto_nodejs_1 = require("@turt2live/matrix-sdk-crypto-nodejs");
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class InternalOlmMachineFactory {
|
||||
constructor(userId, deviceId, engine, storagePath) {
|
||||
this.userId = userId;
|
||||
this.deviceId = deviceId;
|
||||
this.engine = engine;
|
||||
this.storagePath = storagePath;
|
||||
}
|
||||
build() {
|
||||
if (InternalOlmMachineFactory.FACTORY_OVERRIDE) {
|
||||
return InternalOlmMachineFactory.FACTORY_OVERRIDE(this.userId, this.deviceId, this.engine, this.storagePath);
|
||||
}
|
||||
return matrix_sdk_crypto_nodejs_1.OlmMachine.withSledBackend(this.userId, this.deviceId, this.engine, this.storagePath);
|
||||
}
|
||||
}
|
||||
exports.InternalOlmMachineFactory = InternalOlmMachineFactory;
|
41
node_modules/matrix-bot-sdk/lib/e2ee/RoomTracker.d.ts
generated
vendored
Normal file
41
node_modules/matrix-bot-sdk/lib/e2ee/RoomTracker.d.ts
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
import { MatrixClient } from "../MatrixClient";
|
||||
import { ICryptoRoomInformation } from "./ICryptoRoomInformation";
|
||||
/**
|
||||
* Tracks room encryption status for a MatrixClient.
|
||||
* @category Encryption
|
||||
*/
|
||||
export declare class RoomTracker {
|
||||
private client;
|
||||
constructor(client: MatrixClient);
|
||||
/**
|
||||
* Handles a room join
|
||||
* @internal
|
||||
* @param roomId The room ID.
|
||||
*/
|
||||
onRoomJoin(roomId: string): Promise<void>;
|
||||
/**
|
||||
* Handles a room event.
|
||||
* @internal
|
||||
* @param roomId The room ID.
|
||||
* @param event The event.
|
||||
*/
|
||||
onRoomEvent(roomId: string, event: any): Promise<void>;
|
||||
/**
|
||||
* Prepares the room tracker to track the given rooms.
|
||||
* @param {string[]} roomIds The room IDs to track. This should be the joined rooms set.
|
||||
*/
|
||||
prepare(roomIds: string[]): Promise<void>;
|
||||
/**
|
||||
* Queues a room check for the tracker. If the room needs an update to the store, an
|
||||
* update will be made.
|
||||
* @param {string} roomId The room ID to check.
|
||||
*/
|
||||
queueRoomCheck(roomId: string): Promise<void>;
|
||||
/**
|
||||
* Gets the room's crypto configuration, as known by the underlying store. If the room is
|
||||
* not encrypted then this will return an empty object.
|
||||
* @param {string} roomId The room ID to get the config for.
|
||||
* @returns {Promise<ICryptoRoomInformation>} Resolves to the encryption config.
|
||||
*/
|
||||
getRoomCryptoConfig(roomId: string): Promise<ICryptoRoomInformation>;
|
||||
}
|
96
node_modules/matrix-bot-sdk/lib/e2ee/RoomTracker.js
generated
vendored
Normal file
96
node_modules/matrix-bot-sdk/lib/e2ee/RoomTracker.js
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.RoomTracker = void 0;
|
||||
// noinspection ES6RedundantAwait
|
||||
/**
|
||||
* Tracks room encryption status for a MatrixClient.
|
||||
* @category Encryption
|
||||
*/
|
||||
class RoomTracker {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
/**
|
||||
* Handles a room join
|
||||
* @internal
|
||||
* @param roomId The room ID.
|
||||
*/
|
||||
async onRoomJoin(roomId) {
|
||||
await this.queueRoomCheck(roomId);
|
||||
}
|
||||
/**
|
||||
* Handles a room event.
|
||||
* @internal
|
||||
* @param roomId The room ID.
|
||||
* @param event The event.
|
||||
*/
|
||||
async onRoomEvent(roomId, event) {
|
||||
if (event['state_key'] !== '')
|
||||
return; // we don't care about anything else
|
||||
if (event['type'] === 'm.room.encryption' || event['type'] === 'm.room.history_visibility') {
|
||||
await this.queueRoomCheck(roomId);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Prepares the room tracker to track the given rooms.
|
||||
* @param {string[]} roomIds The room IDs to track. This should be the joined rooms set.
|
||||
*/
|
||||
async prepare(roomIds) {
|
||||
for (const roomId of roomIds) {
|
||||
await this.queueRoomCheck(roomId);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Queues a room check for the tracker. If the room needs an update to the store, an
|
||||
* update will be made.
|
||||
* @param {string} roomId The room ID to check.
|
||||
*/
|
||||
async queueRoomCheck(roomId) {
|
||||
const config = await this.client.cryptoStore.getRoom(roomId);
|
||||
if (config) {
|
||||
if (config.algorithm !== undefined) {
|
||||
return; // assume no change to encryption config
|
||||
}
|
||||
}
|
||||
let encEvent;
|
||||
try {
|
||||
encEvent = await this.client.getRoomStateEvent(roomId, "m.room.encryption", "");
|
||||
encEvent.algorithm = encEvent.algorithm ?? 'UNKNOWN';
|
||||
}
|
||||
catch (e) {
|
||||
return; // failure == no encryption
|
||||
}
|
||||
// Pick out the history visibility setting too
|
||||
let historyVisibility;
|
||||
try {
|
||||
const ev = await this.client.getRoomStateEvent(roomId, "m.room.history_visibility", "");
|
||||
historyVisibility = ev.history_visibility;
|
||||
}
|
||||
catch (e) {
|
||||
// ignore - we'll just treat history visibility as normal
|
||||
}
|
||||
await this.client.cryptoStore.storeRoom(roomId, {
|
||||
...encEvent,
|
||||
historyVisibility,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Gets the room's crypto configuration, as known by the underlying store. If the room is
|
||||
* not encrypted then this will return an empty object.
|
||||
* @param {string} roomId The room ID to get the config for.
|
||||
* @returns {Promise<ICryptoRoomInformation>} Resolves to the encryption config.
|
||||
*/
|
||||
async getRoomCryptoConfig(roomId) {
|
||||
let config = await this.client.cryptoStore.getRoom(roomId);
|
||||
if (!config) {
|
||||
await this.queueRoomCheck(roomId);
|
||||
config = await this.client.cryptoStore.getRoom(roomId);
|
||||
}
|
||||
if (!config) {
|
||||
return {};
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
exports.RoomTracker = RoomTracker;
|
||||
//# sourceMappingURL=RoomTracker.js.map
|
1
node_modules/matrix-bot-sdk/lib/e2ee/RoomTracker.js.map
generated
vendored
Normal file
1
node_modules/matrix-bot-sdk/lib/e2ee/RoomTracker.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RoomTracker.js","sourceRoot":"","sources":["../../src/e2ee/RoomTracker.ts"],"names":[],"mappings":";;;AAIA,iCAAiC;AACjC;;;GAGG;AACH,MAAa,WAAW;IACpB,YAA2B,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;IAC/C,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,UAAU,CAAC,MAAc;QAClC,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,WAAW,CAAC,MAAc,EAAE,KAAU;QAC/C,IAAI,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE;YAAE,OAAO,CAAC,oCAAoC;QAC3E,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,mBAAmB,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,2BAA2B,EAAE;YACxF,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;SACrC;IACL,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,OAAO,CAAC,OAAiB;QAClC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE;YAC1B,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;SACrC;IACL,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,cAAc,CAAC,MAAc;QACtC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7D,IAAI,MAAM,EAAE;YACR,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,EAAE;gBAChC,OAAO,CAAC,wCAAwC;aACnD;SACJ;QAED,IAAI,QAAyC,CAAC;QAC9C,IAAI;YACA,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,mBAAmB,EAAE,EAAE,CAAC,CAAC;YAChF,QAAQ,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,IAAI,SAAS,CAAC;SACxD;QAAC,OAAO,CAAC,EAAE;YACR,OAAO,CAAC,2BAA2B;SACtC;QAED,8CAA8C;QAC9C,IAAI,iBAAyB,CAAC;QAC9B,IAAI;YACA,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,2BAA2B,EAAE,EAAE,CAAC,CAAC;YACxF,iBAAiB,GAAG,EAAE,CAAC,kBAAkB,CAAC;SAC7C;QAAC,OAAO,CAAC,EAAE;YACR,yDAAyD;SAC5D;QAED,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE;YAC5C,GAAG,QAAQ;YACX,iBAAiB;SACpB,CAAC,CAAC;IACP,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,mBAAmB,CAAC,MAAc;QAC3C,IAAI,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,EAAE;YACT,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;YAClC,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;SAC1D;QACD,IAAI,CAAC,MAAM,EAAE;YACT,OAAO,EAAE,CAAC;SACb;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AAzFD,kCAyFC"}
|
26
node_modules/matrix-bot-sdk/lib/e2ee/RustEngine.d.ts
generated
vendored
Normal file
26
node_modules/matrix-bot-sdk/lib/e2ee/RustEngine.d.ts
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { OlmMachine } from "@matrix-org/matrix-sdk-crypto-nodejs";
|
||||
import * as AsyncLock from "async-lock";
|
||||
import { MatrixClient } from "../MatrixClient";
|
||||
import { ICryptoRoomInformation } from "./ICryptoRoomInformation";
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export declare const SYNC_LOCK_NAME = "sync";
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export declare class RustEngine {
|
||||
readonly machine: OlmMachine;
|
||||
private client;
|
||||
readonly lock: AsyncLock;
|
||||
constructor(machine: OlmMachine, client: MatrixClient);
|
||||
run(): Promise<void>;
|
||||
private runOnly;
|
||||
addTrackedUsers(userIds: string[]): Promise<void>;
|
||||
prepareEncrypt(roomId: string, roomInfo: ICryptoRoomInformation): Promise<void>;
|
||||
private processKeysClaimRequest;
|
||||
private processKeysUploadRequest;
|
||||
private processKeysQueryRequest;
|
||||
private processToDeviceRequest;
|
||||
private actuallyProcessToDeviceRequest;
|
||||
}
|
132
node_modules/matrix-bot-sdk/lib/e2ee/RustEngine.js
generated
vendored
Normal file
132
node_modules/matrix-bot-sdk/lib/e2ee/RustEngine.js
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.RustEngine = exports.SYNC_LOCK_NAME = void 0;
|
||||
const matrix_sdk_crypto_nodejs_1 = require("@matrix-org/matrix-sdk-crypto-nodejs");
|
||||
const AsyncLock = require("async-lock");
|
||||
const Crypto_1 = require("../models/Crypto");
|
||||
const EncryptionEvent_1 = require("../models/events/EncryptionEvent");
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
exports.SYNC_LOCK_NAME = "sync";
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RustEngine {
|
||||
constructor(machine, client) {
|
||||
this.machine = machine;
|
||||
this.client = client;
|
||||
this.lock = new AsyncLock();
|
||||
}
|
||||
async run() {
|
||||
await this.runOnly(); // run everything, but with syntactic sugar
|
||||
}
|
||||
async runOnly(...types) {
|
||||
// Note: we should not be running this until it runs out, so cache the value into a variable
|
||||
const requests = await this.machine.outgoingRequests();
|
||||
for (const request of requests) {
|
||||
if (types.length && !types.includes(request.type))
|
||||
continue;
|
||||
switch (request.type) {
|
||||
case 0 /* RequestType.KeysUpload */:
|
||||
await this.processKeysUploadRequest(request);
|
||||
break;
|
||||
case 1 /* RequestType.KeysQuery */:
|
||||
await this.processKeysQueryRequest(request);
|
||||
break;
|
||||
case 2 /* RequestType.KeysClaim */:
|
||||
await this.processKeysClaimRequest(request);
|
||||
break;
|
||||
case 3 /* RequestType.ToDevice */:
|
||||
await this.processToDeviceRequest(request);
|
||||
break;
|
||||
case 5 /* RequestType.RoomMessage */:
|
||||
throw new Error("Bindings error: Sending room messages is not supported");
|
||||
case 4 /* RequestType.SignatureUpload */:
|
||||
throw new Error("Bindings error: Backup feature not possible");
|
||||
case 6 /* RequestType.KeysBackup */:
|
||||
throw new Error("Bindings error: Backup feature not possible");
|
||||
default:
|
||||
throw new Error("Bindings error: Unrecognized request type: " + request.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
async addTrackedUsers(userIds) {
|
||||
await this.lock.acquire(exports.SYNC_LOCK_NAME, async () => {
|
||||
const uids = userIds.map(u => new matrix_sdk_crypto_nodejs_1.UserId(u));
|
||||
await this.machine.updateTrackedUsers(uids);
|
||||
const keysClaim = await this.machine.getMissingSessions(uids);
|
||||
if (keysClaim) {
|
||||
await this.processKeysClaimRequest(keysClaim);
|
||||
}
|
||||
});
|
||||
}
|
||||
async prepareEncrypt(roomId, roomInfo) {
|
||||
// TODO: Handle pre-shared invite keys too
|
||||
const members = (await this.client.getJoinedRoomMembers(roomId)).map(u => new matrix_sdk_crypto_nodejs_1.UserId(u));
|
||||
let historyVis = 1 /* HistoryVisibility.Joined */;
|
||||
switch (roomInfo.historyVisibility) {
|
||||
case "world_readable":
|
||||
historyVis = 3 /* HistoryVisibility.WorldReadable */;
|
||||
break;
|
||||
case "invited":
|
||||
historyVis = 0 /* HistoryVisibility.Invited */;
|
||||
break;
|
||||
case "shared":
|
||||
historyVis = 2 /* HistoryVisibility.Shared */;
|
||||
break;
|
||||
case "joined":
|
||||
default:
|
||||
// Default and other cases handled by assignment before switch
|
||||
}
|
||||
const encEv = new EncryptionEvent_1.EncryptionEvent({
|
||||
type: "m.room.encryption",
|
||||
content: roomInfo,
|
||||
});
|
||||
const settings = new matrix_sdk_crypto_nodejs_1.EncryptionSettings();
|
||||
settings.algorithm = roomInfo.algorithm === Crypto_1.EncryptionAlgorithm.MegolmV1AesSha2
|
||||
? 1 /* RustEncryptionAlgorithm.MegolmV1AesSha2 */
|
||||
: undefined;
|
||||
settings.historyVisibility = historyVis;
|
||||
settings.rotationPeriod = BigInt(encEv.rotationPeriodMs);
|
||||
settings.rotationPeriodMessages = BigInt(encEv.rotationPeriodMessages);
|
||||
await this.lock.acquire(exports.SYNC_LOCK_NAME, async () => {
|
||||
await this.machine.updateTrackedUsers(members); // just in case we missed some
|
||||
await this.runOnly(1 /* RequestType.KeysQuery */);
|
||||
const keysClaim = await this.machine.getMissingSessions(members);
|
||||
if (keysClaim) {
|
||||
await this.processKeysClaimRequest(keysClaim);
|
||||
}
|
||||
});
|
||||
await this.lock.acquire(roomId, async () => {
|
||||
const requests = JSON.parse(await this.machine.shareRoomKey(new matrix_sdk_crypto_nodejs_1.RoomId(roomId), members, settings));
|
||||
for (const req of requests) {
|
||||
await this.actuallyProcessToDeviceRequest(req.txn_id, req.event_type, req.messages);
|
||||
}
|
||||
});
|
||||
}
|
||||
async processKeysClaimRequest(request) {
|
||||
const resp = await this.client.doRequest("POST", "/_matrix/client/v3/keys/claim", null, JSON.parse(request.body));
|
||||
await this.machine.markRequestAsSent(request.id, request.type, JSON.stringify(resp));
|
||||
}
|
||||
async processKeysUploadRequest(request) {
|
||||
const body = JSON.parse(request.body);
|
||||
// delete body["one_time_keys"]; // use this to test MSC3983
|
||||
const resp = await this.client.doRequest("POST", "/_matrix/client/v3/keys/upload", null, body);
|
||||
await this.machine.markRequestAsSent(request.id, request.type, JSON.stringify(resp));
|
||||
}
|
||||
async processKeysQueryRequest(request) {
|
||||
const resp = await this.client.doRequest("POST", "/_matrix/client/v3/keys/query", null, JSON.parse(request.body));
|
||||
await this.machine.markRequestAsSent(request.id, request.type, JSON.stringify(resp));
|
||||
}
|
||||
async processToDeviceRequest(request) {
|
||||
const req = JSON.parse(request.body);
|
||||
await this.actuallyProcessToDeviceRequest(req.txn_id, req.event_type, req.messages);
|
||||
}
|
||||
async actuallyProcessToDeviceRequest(id, type, messages) {
|
||||
const resp = await this.client.sendToDevices(type, messages);
|
||||
await this.machine.markRequestAsSent(id, 3 /* RequestType.ToDevice */, JSON.stringify(resp));
|
||||
}
|
||||
}
|
||||
exports.RustEngine = RustEngine;
|
||||
//# sourceMappingURL=RustEngine.js.map
|
1
node_modules/matrix-bot-sdk/lib/e2ee/RustEngine.js.map
generated
vendored
Normal file
1
node_modules/matrix-bot-sdk/lib/e2ee/RustEngine.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RustEngine.js","sourceRoot":"","sources":["../../src/e2ee/RustEngine.ts"],"names":[],"mappings":"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"}
|
19
node_modules/matrix-bot-sdk/lib/e2ee/SdkOlmEngine.d.ts
generated
vendored
Normal file
19
node_modules/matrix-bot-sdk/lib/e2ee/SdkOlmEngine.d.ts
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { DeviceKeys, GenericKeys, KeyClaim, KeyClaimResponse, KeyQueryResults, OlmEngine, OTKCounts, ToDeviceMessages } from "@turt2live/matrix-sdk-crypto-nodejs";
|
||||
import { MatrixClient } from "../MatrixClient";
|
||||
/**
|
||||
* A representation of a rust-sdk OlmEngine for the bot-sdk. You should not need to
|
||||
* instantiate this yourself.
|
||||
* @category Encryption
|
||||
*/
|
||||
export declare class SdkOlmEngine implements OlmEngine {
|
||||
private client;
|
||||
constructor(client: MatrixClient);
|
||||
claimOneTimeKeys(claim: KeyClaim): Promise<KeyClaimResponse>;
|
||||
queryOneTimeKeys(userIds: string[]): Promise<KeyQueryResults>;
|
||||
uploadOneTimeKeys(body: {
|
||||
device_keys?: DeviceKeys;
|
||||
one_time_keys?: GenericKeys;
|
||||
}): Promise<OTKCounts>;
|
||||
getEffectiveJoinedUsersInRoom(roomId: string): Promise<string[]>;
|
||||
sendToDevices(eventType: string, messages: ToDeviceMessages): Promise<void>;
|
||||
}
|
38
node_modules/matrix-bot-sdk/lib/e2ee/SdkOlmEngine.js
generated
vendored
Normal file
38
node_modules/matrix-bot-sdk/lib/e2ee/SdkOlmEngine.js
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.SdkOlmEngine = void 0;
|
||||
/**
|
||||
* A representation of a rust-sdk OlmEngine for the bot-sdk. You should not need to
|
||||
* instantiate this yourself.
|
||||
* @category Encryption
|
||||
*/
|
||||
class SdkOlmEngine {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
claimOneTimeKeys(claim) {
|
||||
const reconstructed = {};
|
||||
for (const userId of Object.keys(claim)) {
|
||||
if (!reconstructed[userId])
|
||||
reconstructed[userId] = {};
|
||||
for (const deviceId of Object.keys(claim[userId])) {
|
||||
reconstructed[userId][deviceId] = claim[userId][deviceId];
|
||||
}
|
||||
}
|
||||
return this.client.claimOneTimeKeys(reconstructed);
|
||||
}
|
||||
queryOneTimeKeys(userIds) {
|
||||
return this.client.getUserDevices(userIds);
|
||||
}
|
||||
uploadOneTimeKeys(body) {
|
||||
return this.client.doRequest("POST", "/_matrix/client/r0/keys/upload", null, body);
|
||||
}
|
||||
getEffectiveJoinedUsersInRoom(roomId) {
|
||||
// TODO: Handle pre-shared invite keys too
|
||||
return this.client.getJoinedRoomMembers(roomId);
|
||||
}
|
||||
sendToDevices(eventType, messages) {
|
||||
return this.client.sendToDevices(eventType, messages);
|
||||
}
|
||||
}
|
||||
exports.SdkOlmEngine = SdkOlmEngine;
|
10
node_modules/matrix-bot-sdk/lib/e2ee/decorators.d.ts
generated
vendored
Normal file
10
node_modules/matrix-bot-sdk/lib/e2ee/decorators.d.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Flags a MatrixClient function as needing end-to-end encryption enabled.
|
||||
* @category Encryption
|
||||
*/
|
||||
export declare function requiresCrypto(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
||||
/**
|
||||
* Flags a CryptoClient function as needing the CryptoClient to be ready.
|
||||
* @category Encryption
|
||||
*/
|
||||
export declare function requiresReady(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
38
node_modules/matrix-bot-sdk/lib/e2ee/decorators.js
generated
vendored
Normal file
38
node_modules/matrix-bot-sdk/lib/e2ee/decorators.js
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.requiresReady = exports.requiresCrypto = void 0;
|
||||
/**
|
||||
* Flags a MatrixClient function as needing end-to-end encryption enabled.
|
||||
* @category Encryption
|
||||
*/
|
||||
function requiresCrypto() {
|
||||
return function (target, propertyKey, descriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
descriptor.value = function (...args) {
|
||||
const client = this; // eslint-disable-line @typescript-eslint/no-this-alias
|
||||
if (!client.crypto) {
|
||||
throw new Error("End-to-end encryption is not enabled");
|
||||
}
|
||||
return originalMethod.apply(this, args);
|
||||
};
|
||||
};
|
||||
}
|
||||
exports.requiresCrypto = requiresCrypto;
|
||||
/**
|
||||
* Flags a CryptoClient function as needing the CryptoClient to be ready.
|
||||
* @category Encryption
|
||||
*/
|
||||
function requiresReady() {
|
||||
return function (target, propertyKey, descriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
descriptor.value = function (...args) {
|
||||
const crypto = this; // eslint-disable-line @typescript-eslint/no-this-alias
|
||||
if (!crypto.isReady) {
|
||||
throw new Error("End-to-end encryption has not initialized");
|
||||
}
|
||||
return originalMethod.apply(this, args);
|
||||
};
|
||||
};
|
||||
}
|
||||
exports.requiresReady = requiresReady;
|
||||
//# sourceMappingURL=decorators.js.map
|
1
node_modules/matrix-bot-sdk/lib/e2ee/decorators.js.map
generated
vendored
Normal file
1
node_modules/matrix-bot-sdk/lib/e2ee/decorators.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../src/e2ee/decorators.ts"],"names":[],"mappings":";;;AAGA;;;GAGG;AACH,SAAgB,cAAc;IAC1B,OAAO,UAAS,MAAW,EAAE,WAAmB,EAAE,UAA8B;QAC5E,MAAM,cAAc,GAAG,UAAU,CAAC,KAAK,CAAC;QACxC,UAAU,CAAC,KAAK,GAAG,UAAS,GAAG,IAAW;YACtC,MAAM,MAAM,GAAiB,IAAI,CAAC,CAAC,uDAAuD;YAC1F,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;gBAChB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;aAC3D;YAED,OAAO,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5C,CAAC,CAAC;IACN,CAAC,CAAC;AACN,CAAC;AAZD,wCAYC;AAED;;;GAGG;AACH,SAAgB,aAAa;IACzB,OAAO,UAAS,MAAW,EAAE,WAAmB,EAAE,UAA8B;QAC5E,MAAM,cAAc,GAAG,UAAU,CAAC,KAAK,CAAC;QACxC,UAAU,CAAC,KAAK,GAAG,UAAS,GAAG,IAAW;YACtC,MAAM,MAAM,GAAiB,IAAI,CAAC,CAAC,uDAAuD;YAC1F,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE;gBACjB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;aAChE;YAED,OAAO,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5C,CAAC,CAAC;IACN,CAAC,CAAC;AACN,CAAC;AAZD,sCAYC"}
|
Reference in New Issue
Block a user