UNPKG

@grouparoo/core

Version:
294 lines (293 loc) 13.4 kB
"use strict"; 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 = {}));