msbot
Version:
MSBot command line tool for manipulating Microsoft Bot Framework .bot files
216 lines • 8.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/
const crypto = require("crypto");
const fsx = require("fs-extra");
const linq_collections_1 = require("linq-collections");
const path = require("path");
const process = require("process");
const uuid = require("uuid");
const models_1 = require("./models");
const schema_1 = require("./schema");
class BotConfig extends models_1.BotConfigModel {
constructor(secret) {
super();
// internal is not serialized
this.internal = {
secretValidated: false
};
this.encryptedProperties = {
endpoint: ['appPassword'],
abs: ['appPassword'],
luis: ['authoringKey', 'subscriptionKey'],
qna: ['subscriptionKey'],
dispatch: ['authoringKey', 'subscriptionKey']
};
this.internal.secret = secret;
}
static async LoadBotFromFolder(folder, secret) {
let files = linq_collections_1.Enumerable.fromSource(await fsx.readdir(folder || process.cwd()))
.where(file => path.extname(file) == '.bot');
if (files.any()) {
return await BotConfig.Load(files.first(), secret);
}
throw new Error(`Error: no bot file found in ${folder}. Choose a different location or use msbot init to create a .bot file."`);
}
// load the config file
static async Load(botpath, secret) {
let bot = new BotConfig(secret);
Object.assign(bot, await fsx.readJson(botpath));
bot.internal.location = botpath;
let hasSecret = (secret && bot.secretKey && bot.secretKey.length > 0);
if (hasSecret)
bot.decryptAll();
return bot;
}
// save the config file
async save(botpath) {
let hasSecret = (this.secretKey && this.secretKey.length > 0);
// make sure that all dispatch serviceIds still match services that are in the bot
for (let service of this.services) {
if (service.type == schema_1.ServiceType.Dispatch) {
let dispatchService = service;
dispatchService.serviceIds = linq_collections_1.Enumerable.fromSource(dispatchService.serviceIds)
.where(serviceId => linq_collections_1.Enumerable.fromSource(this.services).any(s => s.id == serviceId))
.toArray();
}
}
if (hasSecret)
this.encryptAll();
await fsx.writeJson(botpath || this.internal.location, {
name: this.name,
description: this.description,
secretKey: this.secretKey,
services: this.services
}, { spaces: 4 });
if (hasSecret)
this.decryptAll();
}
clearSecret() {
this.validateSecretKey();
this.secretKey = '';
}
// connect to a service
connectService(newService) {
if (linq_collections_1.Enumerable.fromSource(this.services)
.where(s => s.type == newService.type)
.where(s => s.id == newService.id)
.any()) {
throw Error(`service with ${newService.id} already connected`);
}
else {
// give unique name
let nameCount = 1;
let name = newService.name;
while (true) {
if (nameCount > 1) {
name = `${newService.name} (${nameCount})`;
}
if (!linq_collections_1.Enumerable.fromSource(this.services).where(s => s.name == name).any())
break;
nameCount++;
}
newService.name = name;
this.services.push(models_1.BotConfigModel.serviceFromJSON(newService));
}
}
// encrypt all values in the config
encryptAll() {
for (let service of this.services) {
this.encryptService(service);
}
}
// decrypt all values in the config
decryptAll() {
for (let service of this.services) {
this.decryptService(service);
}
}
// encrypt just a service
encryptService(service) {
let encryptedProperties = this.getEncryptedProperties(service.type);
for (let i = 0; i < encryptedProperties.length; i++) {
let prop = encryptedProperties[i];
let val = service[prop];
service[prop] = this.encryptValue(val);
}
return service;
}
// decrypt just a service
decryptService(service) {
let encryptedProperties = this.getEncryptedProperties(service.type);
for (let i = 0; i < encryptedProperties.length; i++) {
let prop = encryptedProperties[i];
let val = service[prop];
service[prop] = this.decryptValue(val);
}
return service;
}
// remove service by name or id
disconnectServiceByNameOrId(nameOrId) {
let svs = new linq_collections_1.List(this.services);
for (let i = 0; i < svs.count(); i++) {
let service = svs.elementAt(i);
if (service.id == nameOrId || service.name == nameOrId) {
svs.removeAt(i);
this.services = svs.toArray();
return service;
}
}
throw new Error(`a service with id or name of [${nameOrId}] was not found`);
}
// remove a service
disconnectService(type, id) {
let svs = new linq_collections_1.List(this.services);
for (let i = 0; i < svs.count(); i++) {
let service = svs.elementAt(i);
if (service.type == type && service.id == id) {
svs.removeAt(i);
this.services = svs.toArray();
return;
}
}
}
encryptValue(value) {
if (!value || value.length == 0)
return value;
if (this.secretKey.length > 0) {
this.validateSecretKey();
return this.internalEncrypt(value);
}
return value;
}
decryptValue(encryptedValue) {
if (!encryptedValue || encryptedValue.length == 0)
return encryptedValue;
if (this.secretKey.length > 0) {
this.validateSecretKey();
return this.internalDecrypt(encryptedValue);
}
return encryptedValue;
}
// make sure secret is correct by decrypting the secretKey with it
validateSecretKey() {
if (this.internal.secretValidated) {
return;
}
if (!this.internal.secret || this.internal.secret.length == 0) {
throw new Error('You are attempting to perform an operation which needs access to the secret and --secret is missing');
}
try {
if (!this.secretKey || this.secretKey.length == 0) {
// if no key, create a guid and enrypt that to use as secret validator
this.secretKey = this.internalEncrypt(uuid());
}
else {
const decipher = crypto.createDecipher('aes192', this.internal.secret);
let value = decipher.update(this.secretKey, 'hex', 'utf8');
value += decipher.final('utf8');
}
this.internal.secretValidated = true;
}
catch (_a) {
throw new Error('You are attempting to perform an operation which needs access to the secret and --secret is incorrect.');
}
}
internalEncrypt(value) {
const cipher = crypto.createCipher('aes192', this.internal.secret);
let encryptedValue = cipher.update(value, 'utf8', 'hex');
encryptedValue += cipher.final('hex');
return encryptedValue;
}
internalDecrypt(encryptedValue) {
const decipher = crypto.createDecipher('aes192', this.internal.secret);
let value = decipher.update(encryptedValue, 'hex', 'utf8');
value += decipher.final('utf8');
return value;
}
getEncryptedProperties(type) {
return this.encryptedProperties[type];
}
}
exports.BotConfig = BotConfig;
//# sourceMappingURL=BotConfig.js.map