UNPKG

n8n

Version:

n8n Workflow Automation Tool

588 lines 27.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); 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 __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LoadNodesAndCredentials = void 0; const backend_common_1 = require("@n8n/backend-common"); const config_1 = require("@n8n/config"); const di_1 = require("@n8n/di"); const utils_1 = require("@n8n/utils"); const fast_glob_1 = __importDefault(require("fast-glob")); const promises_1 = __importDefault(require("fs/promises")); const n8n_core_1 = require("n8n-core"); const n8n_workflow_1 = require("n8n-workflow"); const path_1 = __importDefault(require("path")); const picocolors_1 = __importDefault(require("picocolors")); const constants_1 = require("./constants"); const tool_generation_1 = require("./tool-generation"); let LoadNodesAndCredentials = class LoadNodesAndCredentials { constructor(logger, errorReporter, instanceSettings, globalConfig, moduleRegistry, executionContextHookRegistry) { this.logger = logger; this.errorReporter = errorReporter; this.instanceSettings = instanceSettings; this.globalConfig = globalConfig; this.moduleRegistry = moduleRegistry; this.executionContextHookRegistry = executionContextHookRegistry; this.known = { nodes: {}, credentials: {} }; this.loaded = { nodes: {}, credentials: {} }; this.types = { nodes: [], credentials: [] }; this.loaders = {}; this.excludeNodes = this.globalConfig.nodes.exclude; this.includeNodes = this.globalConfig.nodes.include; this.postProcessors = []; this.augmentNodeTypeDescription = (node) => { const hooks = this.executionContextHookRegistry.getHookForTriggerType(node.name); if (hooks.length > 0) { this.logger.debug(`Found ${hooks.length} hooks for trigger node: ${node.name}`); } if (hooks.length === 0) return; if (node.properties.some((p) => p.name === 'executionsHooksVersion')) return; const allHookValues = [ { displayName: 'User Identifier', name: 'hookName', type: 'options', noDataExpression: true, options: hooks.map((hook) => { const displayName = hook.hookDescription.displayName ?? hook.hookDescription.name; return { name: displayName, value: hook.hookDescription.name, description: `Use ${displayName} hook`, }; }), default: '', description: 'Configure how n8n extracts the identity token of the user triggering a webhook. It is used to run each execution with the correct user.', required: true, }, ]; for (const hook of hooks) { const hookOptions = hook.hookDescription.options ?? []; if (hookOptions.length > 0) { for (const hookOption of hookOptions) { const enhancedOption = { ...hookOption, displayOptions: { ...hookOption.displayOptions, show: { ...hookOption.displayOptions?.show, hookName: [hook.hookDescription.name], }, }, }; allHookValues.push(enhancedOption); } } } const executionsHooksVersion = { displayName: 'Executions Hooks Version', name: 'executionsHooksVersion', type: 'hidden', default: 1, }; const contextHooksProperty = { displayName: 'Identify user for dynamic credentials', name: 'contextEstablishmentHooks', type: 'fixedCollection', placeholder: 'Add User Identifier', default: {}, typeOptions: { multipleValues: true, hideEmptyMessage: true, }, options: [ { name: 'hooks', displayName: 'Hooks', values: allHookValues, }, ], description: 'Configure how n8n extracts the identity token of the user triggering a webhook. It is used to run each execution with the correct user.', }; const contextHooksNotice = { displayName: 'Configure how n8n extracts the identity token of the user triggering a webhook. It is used to run each execution with the correct user.', name: 'contextHooksNotice', type: 'notice', default: '', }; let index = node.properties.findIndex((p) => p.name === 'options'); if (index === -1) { index = node.properties.length; } node.properties.splice(index, 0, contextHooksNotice); node.properties.splice(index, 0, contextHooksProperty); node.properties.splice(index, 0, executionsHooksVersion); }; } async init() { if (backend_common_1.inTest) throw new n8n_workflow_1.UnexpectedError('Not available in tests'); const delimiter = process.platform === 'win32' ? ';' : ':'; process.env.NODE_PATH = [module.paths.join(delimiter), process.env.NODE_PATH] .filter(Boolean) .join(delimiter); module.constructor._initPaths(); if (!constants_1.inE2ETests) { this.excludeNodes = this.excludeNodes ?? []; this.excludeNodes.push('n8n-nodes-base.e2eTest'); } if (process.env.N8N_ENV_FEAT_DYNAMIC_CREDENTIALS !== 'true') { this.excludeNodes = this.excludeNodes ?? []; this.excludeNodes.push('n8n-nodes-base.dynamicCredentialCheck'); } const basePathsToScan = [ path_1.default.join(constants_1.CLI_DIR, '..'), path_1.default.join(constants_1.CLI_DIR, 'node_modules'), ]; for (const nodeModulesDir of basePathsToScan) { await this.loadNodesFromNodeModules(nodeModulesDir, 'n8n-nodes-base'); await this.loadNodesFromNodeModules(nodeModulesDir, '@n8n/n8n-nodes-langchain'); } await this.loadNodesFromCustomDirectories(); for (const loader of this.moduleRegistry.nodeLoaders) { if (loader.packageName in this.loaders) { throw new n8n_workflow_1.UnexpectedError(picocolors_1.default.red(`Node loader ${loader.packageName} is already registered.`)); } try { await loader.loadAll(); this.loaders[loader.packageName] = loader; } catch (error) { this.logger.error(`Failed to load package "${loader.packageName}"`, { error: (0, n8n_workflow_1.ensureError)(error), }); this.errorReporter.error(error, { extra: { packageName: loader.packageName } }); } } await this.postProcessLoaders(); } addPostProcessor(fn) { this.postProcessors.push(fn); } releaseTypes() { this.types = { nodes: [], credentials: [] }; for (const loader of Object.values(this.loaders)) { loader.releaseTypes(); } } async collectTypes() { const needsReload = this.types.nodes.length === 0 && this.types.credentials.length === 0; if (needsReload) { await this.postProcessLoaders(); } const types = { nodes: this.types.nodes, credentials: this.types.credentials, }; if (needsReload) { this.releaseTypes(); } return types; } isKnownNode(type) { return type in this.known.nodes; } get loadedCredentials() { return this.loaded.credentials; } get loadedNodes() { return this.loaded.nodes; } get knownCredentials() { return this.known.credentials; } get knownNodes() { return this.known.nodes; } async loadNodesFromNodeModules(nodeModulesDir, packageName) { const installedPackagePaths = await (0, fast_glob_1.default)(packageName, { cwd: nodeModulesDir, onlyDirectories: true, deep: 1, }); for (const packagePath of installedPackagePaths) { try { await this.runDirectoryLoader(n8n_core_1.LazyPackageDirectoryLoader, path_1.default.join(nodeModulesDir, packagePath)); } catch (error) { this.logger.error(error.message); this.errorReporter.error(error); } } } resolveIcon(packageName, url) { const isCustom = packageName === n8n_core_1.CUSTOM_NODES_PACKAGE_NAME; const loader = this.loaders[packageName]; if (!loader || !(loader instanceof n8n_core_1.DirectoryLoader)) { return undefined; } const resolvePath = (iconPath) => { return path_1.default.resolve(loader.directory, iconPath); }; const resolvePathCustom = (path) => { if ((0, utils_1.isWindowsFilePath)(path)) return path; return path.startsWith('/') ? path : '/' + path; }; const pathPrefix = `/icons/${packageName}/`; const urlFilePath = url.substring(pathPrefix.length); const filePath = isCustom ? resolvePathCustom(urlFilePath) : resolvePath(urlFilePath); return (0, backend_common_1.isContainedWithin)(loader.directory, filePath) ? filePath : undefined; } resolveSchema({ node, version, resource, operation, }) { const nodePath = this.known.nodes[node]?.sourcePath; if (!nodePath) { return undefined; } const nodeParentPath = path_1.default.dirname(nodePath); const schemaPath = ['__schema__', `v${version}`, resource, operation].filter(Boolean).join('/'); const filePath = path_1.default.resolve(nodeParentPath, schemaPath + '.json'); return (0, backend_common_1.isContainedWithin)(nodeParentPath, filePath) ? filePath : undefined; } getCustomDirectories() { const customDirectories = [this.instanceSettings.customExtensionDir]; if (process.env[n8n_core_1.CUSTOM_EXTENSION_ENV] !== undefined) { const customExtensionFolders = process.env[n8n_core_1.CUSTOM_EXTENSION_ENV].split(';'); customDirectories.push(...customExtensionFolders); } return customDirectories; } async loadNodesFromCustomDirectories() { for (const directory of this.getCustomDirectories()) { await this.runDirectoryLoader(n8n_core_1.CustomDirectoryLoader, directory); } } async loadPackage(packageName) { const finalNodeUnpackedPath = path_1.default.join(this.instanceSettings.nodesDownloadDir, 'node_modules', packageName); return await this.runDirectoryLoader(n8n_core_1.PackageDirectoryLoader, finalNodeUnpackedPath); } async unloadPackage(packageName) { if (packageName in this.loaders) { this.loaders[packageName].reset(); delete this.loaders[packageName]; } } supportsProxyAuth(description) { if (!description.credentials) return false; return description.credentials.some(({ name }) => { const credType = this.types.credentials.find((t) => t.name === name); if (!credType) { this.logger.warn(`Failed to load Custom API options for the node "${description.name}": Unknown credential name "${name}"`); return false; } if (credType.authenticate !== undefined) return true; return (Array.isArray(credType.extends) && credType.extends.some((parentType) => ['oAuth2Api', 'googleOAuth2Api', 'oAuth1Api'].includes(parentType))); }); } injectCustomApiCallOptions() { this.types.nodes.forEach((node) => { const isLatestVersion = node.defaultVersion === undefined || node.defaultVersion === node.version; if (isLatestVersion) { if (!this.supportsProxyAuth(node)) return; node.properties.forEach((p) => { if (['resource', 'operation'].includes(p.name) && Array.isArray(p.options) && p.options[p.options.length - 1].name !== constants_1.CUSTOM_API_CALL_NAME) { p.options.push({ name: constants_1.CUSTOM_API_CALL_NAME, value: constants_1.CUSTOM_API_CALL_KEY, }); } }); } }); } shouldInjectContextEstablishmentHooks() { return process.env.N8N_ENV_FEAT_DYNAMIC_CREDENTIALS === 'true'; } injectContextEstablishmentHooks() { const isEnabled = this.shouldInjectContextEstablishmentHooks(); if (!isEnabled) { this.logger.debug('Context establishment hooks feature is disabled'); return; } const triggerNodes = this.types.nodes.filter((node) => node.group.includes('trigger')); this.logger.debug(`Injecting context establishment hooks for ${triggerNodes.length} trigger nodes`); triggerNodes.forEach(this.augmentNodeTypeDescription); } async runDirectoryLoader(constructor, dir) { const loader = new constructor(dir, this.excludeNodes, this.includeNodes); if (loader instanceof n8n_core_1.PackageDirectoryLoader && loader.packageName in this.loaders) { throw new n8n_workflow_1.UserError(picocolors_1.default.red(`nodes package ${loader.packageName} is already loaded.\n Please delete this second copy at path ${dir}`)); } await loader.loadAll(); this.loaders[loader.packageName] = loader; return loader; } async postProcessLoaders() { this.known = { nodes: {}, credentials: {} }; this.loaded = { nodes: {}, credentials: {} }; this.types = { nodes: [], credentials: [] }; for (const loader of Object.values(this.loaders)) { await loader.ensureTypesLoaded(); const { known, types, packageName } = loader; this.types.nodes = this.types.nodes.concat(types.nodes.map(({ name, ...rest }) => ({ ...rest, name: `${packageName}.${name}`, }))); const processedCredentials = types.credentials.map((credential) => { if (this.shouldAddDomainRestrictions(credential)) { const clonedCredential = { ...credential }; clonedCredential.properties = this.injectDomainRestrictionFields([ ...(clonedCredential.properties ?? []), ]); return { ...clonedCredential, supportedNodes: loader instanceof n8n_core_1.PackageDirectoryLoader ? credential.supportedNodes?.map((nodeName) => `${loader.packageName}.${nodeName}`) : undefined, }; } return { ...credential, supportedNodes: loader instanceof n8n_core_1.PackageDirectoryLoader ? credential.supportedNodes?.map((nodeName) => `${loader.packageName}.${nodeName}`) : undefined, }; }); this.types.credentials = this.types.credentials.concat(processedCredentials); for (const credentialTypeName in known.credentials) { const credentialType = loader.getCredential(credentialTypeName); if (this.shouldAddDomainRestrictions(credentialType)) { credentialType.type.properties = this.injectDomainRestrictionFields([ ...(credentialType.type.properties ?? []), ]); } } for (const type in known.nodes) { const { className, sourcePath } = known.nodes[type]; this.known.nodes[`${packageName}.${type}`] = { className, sourcePath: loader.resolveSourcePath(sourcePath), }; } for (const type in known.credentials) { const { className, sourcePath, supportedNodes, extends: extendsArr, } = known.credentials[type]; this.known.credentials[type] = { className, sourcePath: loader.resolveSourcePath(sourcePath), supportedNodes: loader instanceof n8n_core_1.PackageDirectoryLoader ? supportedNodes?.map((nodeName) => `${loader.packageName}.${nodeName}`) : undefined, extends: extendsArr, }; } } (0, tool_generation_1.createAiTools)(this.types, this.known); (0, tool_generation_1.createHitlTools)(this.types, this.known); this.injectCustomApiCallOptions(); this.injectContextEstablishmentHooks(); for (const postProcessor of this.postProcessors) { await postProcessor(); } } recognizesNode(fullNodeType) { const [packageName, nodeType] = fullNodeType.split('.'); const { loaders } = this; const loader = loaders[packageName]; return !!loader && nodeType in loader.known.nodes; } getNode(fullNodeType) { const [packageName, nodeType] = fullNodeType.split('.'); const { loaders } = this; const loader = loaders[packageName]; if (!loader) { throw new n8n_core_1.UnrecognizedNodeTypeError(packageName, nodeType); } const loadedNode = loader.getNode(nodeType); if (this.shouldInjectContextEstablishmentHooks() && 'properties' in loadedNode.type.description) { this.augmentNodeTypeDescription(loadedNode.type.description); } return loadedNode; } getCredential(credentialType) { const { loadedCredentials } = this; for (const loader of Object.values(this.loaders)) { if (credentialType in loader.known.credentials) { const loaded = loader.getCredential(credentialType); loadedCredentials[credentialType] = loaded; } } if (credentialType in loadedCredentials) { return loadedCredentials[credentialType]; } throw new n8n_core_1.UnrecognizedCredentialTypeError(credentialType); } async setupHotReload() { const { default: debounce } = await Promise.resolve().then(() => __importStar(require('lodash/debounce'))); const { subscribe } = await Promise.resolve().then(() => __importStar(require('@parcel/watcher'))); const { Push } = await Promise.resolve().then(() => __importStar(require('./push'))); const push = di_1.Container.get(Push); for (const loader of Object.values(this.loaders)) { if (!(loader instanceof n8n_core_1.DirectoryLoader)) continue; const { directory } = loader; try { await promises_1.default.access(directory); } catch { continue; } const reloader = debounce(async () => { this.logger.info(`Hot reload triggered for ${loader.packageName}`); try { loader.reset(); await loader.loadAll(); await this.postProcessLoaders(); push.broadcast({ type: 'nodeDescriptionUpdated', data: {} }); } catch (error) { this.logger.error(`Hot reload failed for ${loader.packageName}`); } }, 100); const watchPaths = loader.isLazyLoaded ? [path_1.default.join(directory, 'dist')] : [directory]; const customNodesRoot = path_1.default.join(directory, 'node_modules'); if (loader.packageName === n8n_core_1.CUSTOM_NODES_PACKAGE_NAME) { const customNodeEntries = await promises_1.default.readdir(customNodesRoot, { withFileTypes: true, }); const realCustomNodesPaths = await Promise.all(customNodeEntries .filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) && !entry.name.startsWith('.')) .map(async (entry) => await promises_1.default.realpath(path_1.default.join(customNodesRoot, entry.name)).catch(() => null))); watchPaths.push.apply(watchPaths, realCustomNodesPaths.filter((path) => !!path)); } this.logger.debug('Watching node folders for hot reload', { loader: loader.packageName, paths: watchPaths, }); for (const watchPath of watchPaths) { const onFileEvent = async (_error, events) => { if (events.some((event) => event.type !== 'delete')) { const modules = Object.keys(require.cache).filter((module) => module.startsWith(watchPath)); for (const module of modules) { delete require.cache[module]; } await reloader(); } }; const ignore = ['**/node_modules/**/node_modules/**']; await subscribe(watchPath, onFileEvent, { ignore }); } } } shouldAddDomainRestrictions(credential) { const credentialType = 'type' in credential ? credential.type : credential; return (credentialType.authenticate !== undefined || credentialType.genericAuth === true || (Array.isArray(credentialType.extends) && (credentialType.extends.includes('oAuth2Api') || credentialType.extends.includes('oAuth1Api') || credentialType.extends.includes('googleOAuth2Api')))); } injectDomainRestrictionFields(properties) { if (properties.some((prop) => prop.name === 'allowedHttpRequestDomains')) { return properties; } const domainFields = [ { displayName: 'Allowed HTTP Request Domains', name: 'allowedHttpRequestDomains', type: 'options', options: [ { name: 'All', value: 'all', description: 'Allow all requests when used in the HTTP Request node', }, { name: 'Specific Domains', value: 'domains', description: 'Restrict requests to specific domains', }, { name: 'None', value: 'none', description: 'Block all requests when used in the HTTP Request node', }, ], default: 'all', description: 'Control which domains this credential can be used with in HTTP Request nodes', }, { displayName: 'Allowed Domains', name: 'allowedDomains', type: 'string', default: '', placeholder: 'example.com, *.subdomain.com', description: 'Comma-separated list of allowed domains (supports wildcards with *)', displayOptions: { show: { allowedHttpRequestDomains: ['domains'], }, }, }, ]; return [...properties, ...domainFields]; } }; exports.LoadNodesAndCredentials = LoadNodesAndCredentials; exports.LoadNodesAndCredentials = LoadNodesAndCredentials = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, n8n_core_1.ErrorReporter, n8n_core_1.InstanceSettings, config_1.GlobalConfig, backend_common_1.ModuleRegistry, n8n_core_1.ExecutionContextHookRegistry]) ], LoadNodesAndCredentials); //# sourceMappingURL=load-nodes-and-credentials.js.map