@grouparoo/core
Version:
The Grouparoo Core
294 lines (293 loc) • 13.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OptionHelper = exports.ObfuscatedOptionString = void 0;
const actionhero_1 = require("actionhero");
const Option_1 = require("./../models/Option");
const Source_1 = require("./../models/Source");
const Destination_1 = require("./../models/Destination");
const Property_1 = require("../models/Property");
const App_1 = require("./../models/App");
const lockableHelper_1 = require("./lockableHelper");
const pluralize_1 = require("pluralize");
const modelName_1 = require("./modelName");
const sourcesCache_1 = require("../modules/caches/sourcesCache");
exports.ObfuscatedOptionString = "__ObfuscatedOption";
var OptionHelper;
(function (OptionHelper) {
async function getOptions(instance, sourceFromEnvironment = true, obfuscateSensitive = false) {
var _a;
if (sourceFromEnvironment === null || sourceFromEnvironment === undefined) {
sourceFromEnvironment = true;
}
const options = (_a = instance.__options) !== null && _a !== void 0 ? _a : (await Option_1.Option.findAll({
where: {
ownerId: instance.id,
ownerType: (0, modelName_1.modelName)(instance),
},
}));
if (!instance.__options)
instance.__options = options;
const optionsToObfuscate = await getOptionsToObfuscate(instance, obfuscateSensitive);
let optionsObject = await getDefaultOptionValues(instance);
options.forEach((option) => {
if (optionsToObfuscate.includes(option.key)) {
optionsObject[option.key] = exports.ObfuscatedOptionString;
}
else {
optionsObject[option.key] = option.typedValue();
}
});
if (sourceFromEnvironment) {
optionsObject = sourceEnvironmentVariableOptions(instance, optionsObject);
}
return optionsObject;
}
OptionHelper.getOptions = getOptions;
async function prepareOptions(instance, options, sourceFromEnvironment = false) {
const filteredOptions = filterEmptyOptions(options);
const sanitizedOptions = await replaceObfuscatedOptions(instance, filteredOptions, false);
if (sourceFromEnvironment) {
return sourceEnvironmentVariableOptions(instance, {
...sanitizedOptions,
});
}
return sanitizedOptions;
}
async function setOptions(instance, options, externallyValidate = true) {
delete instance.__options;
const sanitizedOptions = await prepareOptions(instance, options, false);
if (typeof instance["validateOptions"] === "function")
await instance.validateOptions(sanitizedOptions, externallyValidate);
const oldOptionsWithoutEnv = await getOptions(instance, false);
const oldOptionsWithEnv = await getOptions(instance, true);
// If we had previously used an ENV string, and the value was returned, assume we meant to use the ENV
// This is helpful for some UI options types (list) which really render the value
for (const key in sanitizedOptions) {
if (oldOptionsWithoutEnv[key] !== undefined &&
oldOptionsWithoutEnv[key] !== oldOptionsWithEnv[key] &&
sanitizedOptions[key] === oldOptionsWithEnv[key]) {
sanitizedOptions[key] = oldOptionsWithoutEnv[key];
}
}
let hasChanges = false;
for (const key in oldOptionsWithoutEnv) {
if (oldOptionsWithoutEnv[key] !== sanitizedOptions[key]) {
hasChanges = true;
}
}
for (const key in sanitizedOptions) {
if (oldOptionsWithoutEnv[key] !== sanitizedOptions[key]) {
hasChanges = true;
}
}
if (!hasChanges)
return;
await lockableHelper_1.LockableHelper.beforeUpdateOptions(instance, hasChanges);
await Option_1.Option.destroy({
where: {
ownerId: instance.id,
ownerType: (0, modelName_1.modelName)(instance),
},
});
const newOptions = [];
for (const [key, value] of Object.entries(sanitizedOptions)) {
const option = await Option_1.Option.create({
ownerId: instance.id,
ownerType: (0, modelName_1.modelName)(instance),
key,
value: String(value),
type: typeof value,
});
newOptions.push(option);
}
instance.__options = newOptions;
await instance.touch();
// if there's an afterSetOptions hook and we want to commit our changes
if (typeof instance["afterSetOptions"] === "function") {
await instance["afterSetOptions"](hasChanges);
}
}
OptionHelper.setOptions = setOptions;
async function getPlugin(instance) {
const type = await getInstanceType(instance);
return getPluginByType(type);
}
OptionHelper.getPlugin = getPlugin;
function getPluginByType(type) {
const foundApps = [];
const foundConnections = [];
let match = { plugin: null, pluginConnection: null, pluginApp: null };
actionhero_1.api.plugins.plugins.forEach((plugin) => {
if (plugin.apps) {
plugin.apps.forEach((pluginApp) => {
foundApps.push(pluginApp.name);
if (pluginApp.name === type) {
match.plugin = plugin;
match.pluginApp = pluginApp;
}
});
}
if (plugin.connections) {
plugin.connections.forEach((pluginConnection) => {
foundConnections.push(pluginConnection.name);
if (pluginConnection.name === type) {
match.plugin = plugin;
match.pluginConnection = pluginConnection;
}
});
}
});
if (!match.plugin) {
const missingType = type.includes("-") || type.includes(":") ? "connection" : "app";
const collection = missingType === "app" ? foundApps : foundConnections;
throw new Error(`Cannot find a "${type}" ${missingType} available within the installed plugins. Current ${(0, pluralize_1.plural)(missingType)} installed are: ${[...collection]
.sort()
.join(", ")}. Use \`grouparoo install\` to add new plugins if necessary.`);
}
return match;
}
OptionHelper.getPluginByType = getPluginByType;
async function validateOptions(instance, options, optionsSpec, allowEmpty = false) {
options = await prepareOptions(instance, options, true);
const type = await getInstanceType(instance);
if (allowEmpty && Object.keys(options).length === 0) {
return;
}
const allOptions = optionsSpec.map((o) => o.key);
const requiredOptions = optionsSpec
.filter((o) => o.required)
.map((o) => o.key);
const optionOptions = optionsSpec
.filter((o) => o.options)
.reduce((optionOptions, opt) => ({
...optionOptions,
[opt.key]: opt.options,
}), {});
requiredOptions.forEach((requiredOption) => {
if (!options[requiredOption]) {
throw new Error(`${requiredOption} is required for a ${(0, modelName_1.modelName)(instance)} of type ${type} (${instance["name"] || instance["key"]}, ${instance.id})`);
}
});
for (const k in options) {
if (allOptions.indexOf(k) < 0) {
throw new Error(`${k} is not an option for a ${type} ${(0, modelName_1.modelName)(instance)} (${instance["name"] || instance["key"]}, ${instance.id})`);
}
const val = options[k].toString();
const opts = optionOptions[k];
if (opts && opts.length > 0 && !opts.includes(val)) {
throw new Error(`"${val}" is not a valid value for ${type} ${(0, modelName_1.modelName)(instance)} option "${k}"`);
}
}
}
OptionHelper.validateOptions = validateOptions;
async function getInstanceType(instance) {
let type = instance["type"];
if (!type || instance instanceof Property_1.Property) {
if (instance["sourceId"]) {
const source = await sourcesCache_1.SourcesCache.findOneWithCache(instance["sourceId"]);
if (source)
type = source.type;
}
}
return type;
}
/**
* Return the list of possible environment variable options for this type,
* GROUPAROO_OPTION__APP__production-hubspot-api-key=abc123 returns production-hubspot-api-key
*/
function getEnvironmentVariableOptionsForTopic(topic) {
const regexp = new RegExp(`^GROUPAROO_OPTION__${topic.toUpperCase()}__(.*)`);
return Object.keys(process.env)
.filter((k) => k.match(regexp))
.map((k) => k.match(regexp)[1]);
}
OptionHelper.getEnvironmentVariableOptionsForTopic = getEnvironmentVariableOptionsForTopic;
/**
* Load the value of an environment variable option from the environment
*/
function getEnvironmentVariableOption(type, key) {
const fullKey = `GROUPAROO_OPTION__${type.toUpperCase()}__${key}`;
const value = process.env[fullKey];
if (!value)
throw new Error(`cannot find environment variable for type=${type} and key=${key} (full key "${fullKey}")`);
return value;
}
OptionHelper.getEnvironmentVariableOption = getEnvironmentVariableOption;
/**
* Replace all values in a bundle of SimpleOptions with those values loaded from the ENV
*/
function sourceEnvironmentVariableOptions(instance, options) {
const envOptionKeys = getEnvironmentVariableOptionsForTopic((0, modelName_1.modelName)(instance));
for (const k in options) {
if (envOptionKeys.includes(options[k].toString()))
options[k] = getEnvironmentVariableOption((0, modelName_1.modelName)(instance), options[k].toString());
}
return options;
}
OptionHelper.sourceEnvironmentVariableOptions = sourceEnvironmentVariableOptions;
async function getDefaultOptionValues(instance) {
const plugin = await getPlugin(instance);
let options = [];
if (instance instanceof App_1.App && plugin.pluginApp) {
options = plugin.pluginApp.options;
}
else if ((instance instanceof Source_1.Source || instance instanceof Destination_1.Destination) &&
plugin.pluginConnection) {
options = plugin.pluginConnection.options;
}
const defaultOptions = {};
for (const opt of options) {
if (opt.defaultValue !== undefined) {
defaultOptions[opt.key] = opt.defaultValue;
}
}
return defaultOptions;
}
function filterEmptyOptions(options) {
const opts = Object.assign({}, options);
Object.keys(opts).forEach((k) => {
if (typeof opts[k] === "undefined" || opts[k] === null || opts[k] === "")
delete opts[k];
});
return opts;
}
OptionHelper.filterEmptyOptions = filterEmptyOptions;
async function getOptionsToObfuscate(instance, obfuscateSensitive = false) {
const optionsToObfuscate = [];
const obfuscatedOptionTypes = ["password", "oauth-token"];
// TODO: only for Apps for now
if (instance instanceof App_1.App) {
const plugin = await instance.getPlugin();
const staticAppOptions = plugin.pluginApp.options;
staticAppOptions.forEach((option) => {
if (obfuscatedOptionTypes.includes(option.type) && obfuscateSensitive) {
optionsToObfuscate.push(option.key);
}
});
const appOptions = await instance.appOptions();
const appOptionKeys = Object.keys(appOptions);
appOptionKeys.forEach((k) => {
if (obfuscatedOptionTypes.includes(appOptions[k].type) &&
obfuscateSensitive) {
optionsToObfuscate.push(k);
}
});
}
return optionsToObfuscate;
}
OptionHelper.getOptionsToObfuscate = getOptionsToObfuscate;
async function replaceObfuscatedOptions(instance, options, sourceFromEnvironment = true) {
let sanitizedOptions = Object.assign({}, options);
const optionsFromDatabase = await getOptions(instance, sourceFromEnvironment, false);
if (Object.keys(sanitizedOptions).length === 0) {
sanitizedOptions = optionsFromDatabase;
}
for (const key of Object.keys(sanitizedOptions)) {
if (sanitizedOptions[key] === exports.ObfuscatedOptionString) {
sanitizedOptions[key] = optionsFromDatabase[key];
}
}
return sanitizedOptions;
}
OptionHelper.replaceObfuscatedOptions = replaceObfuscatedOptions;
})(OptionHelper = exports.OptionHelper || (exports.OptionHelper = {}));