@rocket.chat/forked-matrix-bot-sdk
Version:
TypeScript/JavaScript SDK for Matrix bots and appservices
236 lines (235 loc) • 11.4 kB
JavaScript
;
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;