latest build
This commit is contained in:
parent
d6651b7437
commit
abcc417d78
112
dist/bot.js
vendored
112
dist/bot.js
vendored
@ -23,6 +23,8 @@ var matrix = _interopRequireWildcard(require("matrix-js-sdk"));
|
|||||||
|
|
||||||
var _logger = _interopRequireDefault(require("./logger"));
|
var _logger = _interopRequireDefault(require("./logger"));
|
||||||
|
|
||||||
|
var _encryptAttachment = _interopRequireDefault(require("./encrypt-attachment"));
|
||||||
|
|
||||||
global.Olm = require("olm");
|
global.Olm = require("olm");
|
||||||
|
|
||||||
class OcrccBot {
|
class OcrccBot {
|
||||||
@ -100,7 +102,7 @@ class OcrccBot {
|
|||||||
await this.sendMessage(roomId, content);
|
await this.sendMessage(roomId, content);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
_logger.default.log("error", `ERROR SENDING MESSAGE: ${err}`);
|
_logger.default.log("error", `ERROR SENDING MESSAGE ${content.body}: ${err}`);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -289,28 +291,55 @@ class OcrccBot {
|
|||||||
this.sendTextMessage(roomId, "There is no transcript for this chat.", senderId);
|
this.sendTextMessage(roomId, "There is no transcript for this chat.", senderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filename = path.basename(transcriptFile) || "Transcript";
|
if (this.client.isRoomEncrypted(roomId)) {
|
||||||
const file = fs.readFileSync(transcriptFile);
|
let encryptInfo;
|
||||||
const stats = fs.statSync(transcriptFile);
|
const filename = path.basename(transcriptFile) || "Transcript";
|
||||||
const url = await this.client.uploadContent(file, {
|
const data = fs.readFileSync(transcriptFile);
|
||||||
rawResponse: false,
|
const encryptResult = await _encryptAttachment.default.encryptAttachment(data);
|
||||||
name: filename
|
const buffer = Buffer.from(encryptResult.data);
|
||||||
});
|
encryptInfo = encryptResult.info;
|
||||||
|
const url = await this.client.uploadContent(buffer, {
|
||||||
_logger.default.log('info', url);
|
rawResponse: false,
|
||||||
|
name: filename
|
||||||
const content = {
|
});
|
||||||
msgtype: "m.file",
|
encryptInfo.url = url.content_uri;
|
||||||
body: filename,
|
encryptInfo.mimetype = 'text/plain';
|
||||||
info: {
|
const content = {
|
||||||
size: stats.size,
|
msgtype: "m.file",
|
||||||
|
body: filename,
|
||||||
|
info: {
|
||||||
|
mimetype: 'text/plain'
|
||||||
|
},
|
||||||
|
file: encryptInfo,
|
||||||
|
url: url.content_uri,
|
||||||
|
showToUser: senderId,
|
||||||
mimetype: 'text/plain'
|
mimetype: 'text/plain'
|
||||||
},
|
};
|
||||||
url: url.content_uri,
|
this.sendMessage(roomId, content);
|
||||||
showToUser: senderId,
|
} else {
|
||||||
mimetype: 'text/plain'
|
const filename = path.basename(transcriptFile) || "Transcript";
|
||||||
};
|
const file = fs.readFileSync(transcriptFile);
|
||||||
this.sendMessage(roomId, content);
|
const stats = fs.statSync(transcriptFile);
|
||||||
|
const url = await this.client.uploadContent(file, {
|
||||||
|
rawResponse: false,
|
||||||
|
name: filename
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.default.log('info', url);
|
||||||
|
|
||||||
|
const content = {
|
||||||
|
msgtype: "m.file",
|
||||||
|
body: filename,
|
||||||
|
info: {
|
||||||
|
size: stats.size,
|
||||||
|
mimetype: 'text/plain'
|
||||||
|
},
|
||||||
|
url: url.content_uri,
|
||||||
|
showToUser: senderId,
|
||||||
|
mimetype: 'text/plain'
|
||||||
|
};
|
||||||
|
this.sendMessage(roomId, content);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
_logger.default.log("error", `ERROR UPLOADING CONTENT: ${err}`);
|
_logger.default.log("error", `ERROR UPLOADING CONTENT: ${err}`);
|
||||||
|
|
||||||
@ -455,25 +484,30 @@ class OcrccBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (member.membership === "leave" && member.userId !== this.config.BOT_USERID) {
|
if (member.membership === "leave" && member.userId !== this.config.BOT_USERID) {
|
||||||
const facilitatorId = this.localStorage.getItem(`${member.roomId}-facilitator`);
|
// ensure bot is still in the room
|
||||||
|
const roomData = await this.client.getJoinedRooms();
|
||||||
if (member.userId === facilitatorId) {
|
const joinedRooms = roomData["joined_rooms"];
|
||||||
this.sendTextMessage(member.roomId, `${member.name} has left the chat.`);
|
const isBotInRoom = joinedRooms.includes(member.roomId);
|
||||||
} // leave if there is nobody in the room
|
|
||||||
|
|
||||||
|
|
||||||
const room = this.client.getRoom(member.roomId);
|
const room = this.client.getRoom(member.roomId);
|
||||||
if (!room) return;
|
if (!room) return; // leave if there is nobody in the room
|
||||||
|
|
||||||
const memberCount = room.getJoinedMemberCount();
|
const memberCount = room.getJoinedMemberCount();
|
||||||
|
|
||||||
if (memberCount === 1) {
|
if (memberCount === 1 && isBotInRoom) {
|
||||||
// just the bot left
|
// just the bot left
|
||||||
_logger.default.log("info", `LEAVING EMPTY ROOM ==> ${member.roomId}`);
|
_logger.default.log("info", `LEAVING EMPTY ROOM ==> ${member.roomId}`);
|
||||||
|
|
||||||
this.deleteTranscript(member.userId, member.roomId);
|
this.deleteTranscript(member.userId, member.roomId);
|
||||||
this.localStorage.removeItem(`${member.roomId}-facilitator`);
|
this.localStorage.removeItem(`${member.roomId}-facilitator`);
|
||||||
this.localStorage.removeItem(`${member.roomId}-transcript`);
|
this.localStorage.removeItem(`${member.roomId}-transcript`);
|
||||||
this.client.leave(member.roomId);
|
return this.client.leave(member.roomId);
|
||||||
|
} // notify room if the facilitator has left
|
||||||
|
|
||||||
|
|
||||||
|
const facilitatorId = this.localStorage.getItem(`${member.roomId}-facilitator`);
|
||||||
|
|
||||||
|
if (isBotInRoom && member.userId === facilitatorId) {
|
||||||
|
this.sendTextMessage(member.roomId, `${member.name} has left the chat.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -531,14 +565,20 @@ class OcrccBot {
|
|||||||
sessionStore: new matrix.WebStorageSessionStore(localStorage)
|
sessionStore: new matrix.WebStorageSessionStore(localStorage)
|
||||||
};
|
};
|
||||||
this.client = matrix.createClient(opts);
|
this.client = matrix.createClient(opts);
|
||||||
await this.deleteOldDevices();
|
|
||||||
await this.trackJoinedRooms();
|
|
||||||
await this.client.initCrypto();
|
await this.client.initCrypto();
|
||||||
await this.setMembershipListeners();
|
|
||||||
await this.setMessageListeners();
|
|
||||||
this.client.startClient({
|
this.client.startClient({
|
||||||
initialSyncLimit: 0
|
initialSyncLimit: 0
|
||||||
});
|
});
|
||||||
|
this.client.once('sync', async (state, prevState, data) => {
|
||||||
|
_logger.default.log("info", `SYNC STATUS: ${state}`);
|
||||||
|
|
||||||
|
if (state === 'PREPARED') {
|
||||||
|
await this.deleteOldDevices();
|
||||||
|
await this.trackJoinedRooms();
|
||||||
|
await this.setMembershipListeners();
|
||||||
|
await this.setMessageListeners();
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.handleBotCrash(undefined, err);
|
this.handleBotCrash(undefined, err);
|
||||||
|
|
||||||
|
173
dist/encrypt-attachment.js
vendored
Normal file
173
dist/encrypt-attachment.js
vendored
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// this is from https://github.com/matrix-org/browser-encrypt-attachment
|
||||||
|
// which is the library used by matrix-reack-sdk to encrypt and decrypt attachments
|
||||||
|
// just dropped in node-webcrypto-ossl to replace window.crypto
|
||||||
|
// and Buffer for base64 encoding/decoding instead of window.btoa/window.atob
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt an attachment.
|
||||||
|
* @param {ArrayBuffer} plaintextBuffer The attachment data buffer.
|
||||||
|
* @return {Promise} A promise that resolves with an object when the attachment is encrypted.
|
||||||
|
* The object has a "data" key with an ArrayBuffer of encrypted data and an "info" key
|
||||||
|
* with an object containing the info needed to decrypt the data.
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
Crypto
|
||||||
|
} = require("node-webcrypto-ossl");
|
||||||
|
|
||||||
|
const crypto = new Crypto();
|
||||||
|
|
||||||
|
function encryptAttachment(plaintextBuffer) {
|
||||||
|
var cryptoKey; // The AES key object.
|
||||||
|
|
||||||
|
var exportedKey; // The AES key exported as JWK.
|
||||||
|
|
||||||
|
var ciphertextBuffer; // ArrayBuffer of encrypted data.
|
||||||
|
|
||||||
|
var sha256Buffer; // ArrayBuffer of digest.
|
||||||
|
|
||||||
|
var ivArray; // Uint8Array of AES IV
|
||||||
|
// Generate an IV where the first 8 bytes are random and the high 8 bytes
|
||||||
|
// are zero. We set the counter low bits to 0 since it makes it unlikely
|
||||||
|
// that the 64 bit counter will overflow.
|
||||||
|
|
||||||
|
ivArray = new Uint8Array(16);
|
||||||
|
crypto.getRandomValues(ivArray.subarray(0, 8)); // Load the encryption key.
|
||||||
|
|
||||||
|
return crypto.subtle.generateKey({
|
||||||
|
"name": "AES-CTR",
|
||||||
|
length: 256
|
||||||
|
}, true, ["encrypt", "decrypt"]).then(function (generateKeyResult) {
|
||||||
|
cryptoKey = generateKeyResult; // Export the Key as JWK.
|
||||||
|
|
||||||
|
return crypto.subtle.exportKey("jwk", cryptoKey);
|
||||||
|
}).then(function (exportKeyResult) {
|
||||||
|
exportedKey = exportKeyResult; // Encrypt the input ArrayBuffer.
|
||||||
|
// Use half of the iv as the counter by setting the "length" to 64.
|
||||||
|
|
||||||
|
return crypto.subtle.encrypt({
|
||||||
|
name: "AES-CTR",
|
||||||
|
counter: ivArray,
|
||||||
|
length: 64
|
||||||
|
}, cryptoKey, plaintextBuffer);
|
||||||
|
}).then(function (encryptResult) {
|
||||||
|
ciphertextBuffer = encryptResult; // SHA-256 the encrypted data.
|
||||||
|
|
||||||
|
return crypto.subtle.digest("SHA-256", ciphertextBuffer);
|
||||||
|
}).then(function (digestResult) {
|
||||||
|
sha256Buffer = digestResult;
|
||||||
|
return {
|
||||||
|
data: ciphertextBuffer,
|
||||||
|
info: {
|
||||||
|
v: "v2",
|
||||||
|
key: exportedKey,
|
||||||
|
iv: encodeBase64(ivArray),
|
||||||
|
hashes: {
|
||||||
|
sha256: encodeBase64(new Uint8Array(sha256Buffer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decrypt an attachment.
|
||||||
|
* @param {ArrayBuffer} ciphertextBuffer The encrypted attachment data buffer.
|
||||||
|
* @param {Object} info The information needed to decrypt the attachment.
|
||||||
|
* @param {Object} info.key AES-CTR JWK key object.
|
||||||
|
* @param {string} info.iv Base64 encoded 16 byte AES-CTR IV.
|
||||||
|
* @param {string} info.hashes.sha256 Base64 encoded SHA-256 hash of the ciphertext.
|
||||||
|
* @return {Promise} A promise that resolves with an ArrayBuffer when the attachment is decrypted.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
function decryptAttachment(ciphertextBuffer, info) {
|
||||||
|
if (info === undefined || info.key === undefined || info.iv === undefined || info.hashes === undefined || info.hashes.sha256 === undefined) {
|
||||||
|
throw new Error("Invalid info. Missing info.key, info.iv or info.hashes.sha256 key");
|
||||||
|
}
|
||||||
|
|
||||||
|
var cryptoKey; // The AES key object.
|
||||||
|
|
||||||
|
var ivArray = decodeBase64(info.iv);
|
||||||
|
var expectedSha256base64 = info.hashes.sha256; // Load the AES from the "key" key of the info object.
|
||||||
|
|
||||||
|
return crypto.subtle.importKey("jwk", info.key, {
|
||||||
|
"name": "AES-CTR"
|
||||||
|
}, false, ["encrypt", "decrypt"]).then(function (importKeyResult) {
|
||||||
|
cryptoKey = importKeyResult; // Check the sha256 hash
|
||||||
|
|
||||||
|
return crypto.subtle.digest("SHA-256", ciphertextBuffer);
|
||||||
|
}).then(function (digestResult) {
|
||||||
|
if (encodeBase64(new Uint8Array(digestResult)) != expectedSha256base64) {
|
||||||
|
throw new Error("Mismatched SHA-256 digest");
|
||||||
|
}
|
||||||
|
|
||||||
|
var counterLength;
|
||||||
|
|
||||||
|
if (info.v == "v1" || info.v == "v2") {
|
||||||
|
// Version 1 and 2 use a 64 bit counter.
|
||||||
|
counterLength = 64;
|
||||||
|
} else {
|
||||||
|
// Version 0 uses a 128 bit counter.
|
||||||
|
counterLength = 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.subtle.decrypt({
|
||||||
|
name: "AES-CTR",
|
||||||
|
counter: ivArray,
|
||||||
|
length: counterLength
|
||||||
|
}, cryptoKey, ciphertextBuffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode a typed array of uint8 as base64.
|
||||||
|
* @param {Uint8Array} uint8Array The data to encode.
|
||||||
|
* @return {string} The base64 without padding.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
function encodeBase64(uint8Array) {
|
||||||
|
// Misinterpt the Uint8Array as Latin-1.
|
||||||
|
// window.btoa expects a unicode string with codepoints in the range 0-255.
|
||||||
|
var latin1String = String.fromCharCode.apply(null, uint8Array); // Use the builtin base64 encoder.
|
||||||
|
// var paddedBase64 = window.btoa(latin1String);
|
||||||
|
|
||||||
|
var paddedBase64 = Buffer.from(latin1String, 'binary').toString('base64'); // Calculate the unpadded length.
|
||||||
|
|
||||||
|
var inputLength = uint8Array.length;
|
||||||
|
var outputLength = 4 * Math.floor((inputLength + 2) / 3) + (inputLength + 2) % 3 - 2; // Return the unpadded base64.
|
||||||
|
|
||||||
|
return paddedBase64.slice(0, outputLength);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode a base64 string to a typed array of uint8.
|
||||||
|
* This will decode unpadded base64, but will also accept base64 with padding.
|
||||||
|
* @param {string} base64 The unpadded base64 to decode.
|
||||||
|
* @return {Uint8Array} The decoded data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
function decodeBase64(base64) {
|
||||||
|
// Pad the base64 up to the next multiple of 4.
|
||||||
|
var paddedBase64 = base64 + "===".slice(0, (4 - base64.length % 4) % 4); // Decode the base64 as a misinterpreted Latin-1 string.
|
||||||
|
// window.atob returns a unicode string with codepoints in the range 0-255.
|
||||||
|
// var latin1String = window.atob(paddedBase64);
|
||||||
|
|
||||||
|
var latin1String = Buffer.from(paddedBase64, 'base64').toString('binary'); // Encode the string as a Uint8Array as Latin-1.
|
||||||
|
|
||||||
|
var uint8Array = new Uint8Array(latin1String.length);
|
||||||
|
|
||||||
|
for (var i = 0; i < latin1String.length; i++) {
|
||||||
|
uint8Array[i] = latin1String.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
exports.encryptAttachment = encryptAttachment;
|
||||||
|
exports.decryptAttachment = decryptAttachment;
|
||||||
|
} catch (e) {// Ignore unknown variable "exports" errors when this is loaded directly into a browser
|
||||||
|
// This means that we can test it without having to use browserify.
|
||||||
|
// The intention is that the library is used using browserify.
|
||||||
|
}
|
@ -1,15 +1,9 @@
|
|||||||
const { Crypto } = require("node-webcrypto-ossl");
|
|
||||||
|
|
||||||
const crypto = new Crypto();
|
|
||||||
|
|
||||||
|
|
||||||
// this is from https://github.com/matrix-org/browser-encrypt-attachment
|
// this is from https://github.com/matrix-org/browser-encrypt-attachment
|
||||||
// which is the library used by matrix-reack-sdk to encrypt and decrypt attachments
|
// which is the library used by matrix-reack-sdk to encrypt and decrypt attachments
|
||||||
// just dropped in node-webcrypto-ossl to replace window.crypto
|
// just dropped in node-webcrypto-ossl to replace window.crypto
|
||||||
// and Buffer for base64 encoding/decoding instead of window.btoa/window.atob
|
// and Buffer for base64 encoding/decoding instead of window.btoa/window.atob
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt an attachment.
|
* Encrypt an attachment.
|
||||||
* @param {ArrayBuffer} plaintextBuffer The attachment data buffer.
|
* @param {ArrayBuffer} plaintextBuffer The attachment data buffer.
|
||||||
@ -17,6 +11,10 @@ const crypto = new Crypto();
|
|||||||
* The object has a "data" key with an ArrayBuffer of encrypted data and an "info" key
|
* The object has a "data" key with an ArrayBuffer of encrypted data and an "info" key
|
||||||
* with an object containing the info needed to decrypt the data.
|
* with an object containing the info needed to decrypt the data.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const { Crypto } = require("node-webcrypto-ossl");
|
||||||
|
const crypto = new Crypto();
|
||||||
|
|
||||||
function encryptAttachment(plaintextBuffer) {
|
function encryptAttachment(plaintextBuffer) {
|
||||||
var cryptoKey; // The AES key object.
|
var cryptoKey; // The AES key object.
|
||||||
var exportedKey; // The AES key exported as JWK.
|
var exportedKey; // The AES key exported as JWK.
|
||||||
|
Loading…
Reference in New Issue
Block a user