UNPKG

@rocket.chat/forked-matrix-bot-sdk

Version:

TypeScript/JavaScript SDK for Matrix bots and appservices

236 lines (235 loc) 11.4 kB
"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); }; 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.CryptoClient = void 0; const LogService_1 = require("../logging/LogService"); const Crypto_1 = require("../models/Crypto"); 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 forked_matrix_sdk_crypto_nodejs_1 = require("@rocket.chat/forked-matrix-sdk-crypto-nodejs"); const SdkOlmEngine_1 = require("./SdkOlmEngine"); const InternalOlmMachineFactory_1 = require("./InternalOlmMachineFactory"); /** * 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. */ prepare(roomIds) { return __awaiter(this, void 0, void 0, function* () { yield this.roomTracker.prepare(roomIds); if (this.ready) return; // stop re-preparing here const storedDeviceId = yield this.client.cryptoStore.getDeviceId(); if (storedDeviceId) { this.deviceId = storedDeviceId; } else { const deviceId = (yield this.client.getWhoAmI())['device_id']; if (!deviceId) { throw new Error("Encryption not possible: server not revealing device ID"); } this.deviceId = deviceId; yield this.client.cryptoStore.setDeviceId(this.deviceId); } LogService_1.LogService.debug("CryptoClient", "Starting with device ID:", this.deviceId); this.machine = new InternalOlmMachineFactory_1.InternalOlmMachineFactory(yield this.client.getUserId(), this.deviceId, new SdkOlmEngine_1.SdkOlmEngine(this.client), this.storage.storagePath).build(); yield this.machine.runEngine(); const identity = this.machine.identityKeys; this.deviceCurve25519 = identity[Crypto_1.DeviceKeyAlgorithm.Curve25519]; this.deviceEd25519 = identity[Crypto_1.DeviceKeyAlgorithm.Ed25519]; this.ready = true; }); } /** * 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) { return __awaiter(this, void 0, void 0, function* () { const config = yield this.roomTracker.getRoomCryptoConfig(roomId); return !!(config === null || config === void 0 ? void 0 : config.algorithm); }); } /** * Updates the client's sync-related data. * @param {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, otkCounts, unusedFallbackKeyAlgs, changedDeviceLists, leftDeviceLists) { return __awaiter(this, void 0, void 0, function* () { yield this.machine.pushSync(toDeviceMessages, { changed: changedDeviceLists, left: leftDeviceLists }, otkCounts, unusedFallbackKeyAlgs); }); } /** * Signs an object using the device keys. * @param {object} obj The object to sign. * @returns {Promise<Signatures>} The signatures for the object. */ sign(obj) { return __awaiter(this, void 0, void 0, function* () { obj = JSON.parse(JSON.stringify(obj)); const existingSignatures = obj['signatures'] || {}; delete obj['signatures']; delete obj['unsigned']; const sig = yield this.machine.sign(obj); return Object.assign(Object.assign({}, 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. */ encryptRoomEvent(roomId, eventType, content) { return __awaiter(this, void 0, void 0, function* () { if (!(yield this.isRoomEncrypted(roomId))) { throw new Error("Room is not encrypted"); } const encrypted = yield this.machine.encryptRoomEvent(roomId, eventType, content); 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. */ decryptRoomEvent(event, roomId) { return __awaiter(this, void 0, void 0, function* () { const decrypted = yield this.machine.decryptRoomEvent(roomId, event.raw); return new RoomEvent_1.RoomEvent(Object.assign(Object.assign({}, event.raw), { type: decrypted.clearEvent.type || "io.t2bot.unknown", content: (typeof (decrypted.clearEvent.content) === 'object') ? decrypted.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. */ encryptMedia(file) { return __awaiter(this, void 0, void 0, function* () { const encrypted = (0, forked_matrix_sdk_crypto_nodejs_1.encryptFile)(file); return { buffer: encrypted.data, file: { iv: encrypted.file.iv, key: encrypted.file.web_key, v: encrypted.file.v, hashes: encrypted.file.hashes, }, }; }); } /** * 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) { return __awaiter(this, void 0, void 0, function* () { return (0, forked_matrix_sdk_crypto_nodejs_1.decryptFile)((yield this.client.downloadContent(file.url)).data, Object.assign(Object.assign({}, file), { web_key: file.key })); }); } } __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); exports.CryptoClient = CryptoClient;