UNPKG

n8n-nodes-base

Version:

Base nodes of n8n

379 lines 15.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 __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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkResponseModeConfiguration = exports.isIpAllowed = exports.setupOutputConnection = exports.configuredOutputs = exports.getResponseData = exports.getResponseCode = void 0; exports.validateWebhookAuthentication = validateWebhookAuthentication; exports.handleFormData = handleFormData; exports.generateFormPostBasicAuthToken = generateFormPostBasicAuthToken; exports.generateBasicAuthToken = generateBasicAuthToken; const basic_auth_1 = __importDefault(require("basic-auth")); const promises_1 = require("fs/promises"); const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); const n8n_workflow_1 = require("n8n-workflow"); const a = __importStar(require("node:assert")); const node_crypto_1 = require("node:crypto"); const node_net_1 = require("node:net"); const error_1 = require("./error"); const utilities_1 = require("../../utils/utilities"); const getResponseCode = (parameters) => { if (parameters.responseCode) { return parameters.responseCode; } const responseCodeOptions = parameters.options; if (responseCodeOptions?.responseCode?.values) { const { responseCode, customCode } = responseCodeOptions.responseCode.values; if (customCode) { return customCode; } return responseCode; } return 200; }; exports.getResponseCode = getResponseCode; const getResponseData = (parameters) => { const { responseData, responseMode, options } = parameters; if (responseData) return responseData; if (responseMode === 'onReceived') { const data = options?.responseData; if (data) return data; } if (options?.noResponseBody) return 'noData'; return undefined; }; exports.getResponseData = getResponseData; const configuredOutputs = (parameters) => { const httpMethod = parameters.httpMethod; if (!Array.isArray(httpMethod)) return [ { type: 'main', displayName: httpMethod, }, ]; const outputs = httpMethod.map((method) => { return { type: 'main', displayName: method, }; }); return outputs; }; exports.configuredOutputs = configuredOutputs; const setupOutputConnection = (ctx, method, additionalData) => { const httpMethod = ctx.getNodeParameter('httpMethod', []); let webhookUrl = ctx.getNodeWebhookUrl('default'); const executionMode = ctx.getMode() === 'manual' ? 'test' : 'production'; if (executionMode === 'test') { webhookUrl = webhookUrl.replace('/webhook/', '/webhook-test/'); } // multi methods could be set in settings of node, so we need to check if it's an array if (!Array.isArray(httpMethod)) { return (outputData) => { outputData.json.webhookUrl = webhookUrl; outputData.json.executionMode = executionMode; if (additionalData?.jwtPayload) { outputData.json.jwtPayload = additionalData.jwtPayload; } return [[outputData]]; }; } const outputIndex = httpMethod.indexOf(method.toUpperCase()); const outputs = httpMethod.map(() => []); return (outputData) => { outputData.json.webhookUrl = webhookUrl; outputData.json.executionMode = executionMode; if (additionalData?.jwtPayload) { outputData.json.jwtPayload = additionalData.jwtPayload; } outputs[outputIndex] = [outputData]; return outputs; }; }; exports.setupOutputConnection = setupOutputConnection; const isIpAllowed = (allowlist, ips, ip) => { if (allowlist === undefined || allowlist === '') { return true; } if (!Array.isArray(allowlist)) { allowlist = allowlist.split(',').map((entry) => entry.trim()); } const allowList = getAllowList(allowlist); // Check the primary IP address with proper family detection if (ip) { const ipFamily = (0, node_net_1.isIPv6)(ip) ? 'ipv6' : 'ipv4'; if (allowList.check(ip, ipFamily)) { return true; } } // Check proxy IPs with proper family detection if (ips.some((ipEntry) => { const ipFamily = (0, node_net_1.isIPv6)(ipEntry) ? 'ipv6' : 'ipv4'; return allowList.check(ipEntry, ipFamily); })) { return true; } return false; }; exports.isIpAllowed = isIpAllowed; const getAllowList = (allowlist) => { const allowList = new node_net_1.BlockList(); for (const entry of allowlist) { try { // Check if entry is in CIDR notation (contains /) if (entry.includes('/')) { const [network, prefixStr] = entry.split('/'); const prefix = parseInt(prefixStr, 10); // Validate prefix is a number if (isNaN(prefix)) { continue; } // Detect IP type (IPv4 vs IPv6) const type = network.includes(':') ? 'ipv6' : 'ipv4'; // Validate prefix range const maxPrefix = type === 'ipv4' ? 32 : 128; if (prefix < 0 || prefix > maxPrefix) { continue; } allowList.addSubnet(network, prefix, type); } else { // Single IP address const type = entry.includes(':') ? 'ipv6' : 'ipv4'; allowList.addAddress(entry, type); } } catch { // Ignore invalid entries } } return allowList; }; const checkResponseModeConfiguration = (context) => { const responseMode = context.getNodeParameter('responseMode', 'onReceived'); const connectedNodes = context.getChildNodes(context.getNode().name); const isRespondToWebhookConnected = connectedNodes.some((node) => node.type === 'n8n-nodes-base.respondToWebhook'); if (!isRespondToWebhookConnected && responseMode === 'responseNode') { throw new n8n_workflow_1.WorkflowConfigurationError(context.getNode(), new Error('No Respond to Webhook node found in the workflow'), { description: 'Insert a Respond to Webhook node to your workflow to respond to the webhook or choose another option for the “Respond” parameter', }); } if (isRespondToWebhookConnected && !['responseNode', 'streaming'].includes(responseMode)) { throw new n8n_workflow_1.WorkflowConfigurationError(context.getNode(), new Error('Unused Respond to Webhook node found in the workflow'), { description: 'Set the “Respond” parameter to “Using Respond to Webhook Node” or remove the Respond to Webhook node', }); } }; exports.checkResponseModeConfiguration = checkResponseModeConfiguration; async function validateWebhookAuthentication(ctx, authPropertyName) { const authentication = ctx.getNodeParameter(authPropertyName); if (authentication === 'none') return; const req = ctx.getRequestObject(); const headers = ctx.getHeaderData(); if (authentication === 'basicAuth') { // Basic authorization is needed to call webhook let expectedAuth; try { expectedAuth = await ctx.getCredentials('httpBasicAuth'); } catch { } if (expectedAuth === undefined || !expectedAuth.user || !expectedAuth.password) { // Data is not defined on node so can not authenticate throw new error_1.WebhookAuthorizationError(500, 'No authentication data defined on node!'); } const providedAuth = (0, basic_auth_1.default)(req); // Authorization data is missing if (!providedAuth) { const authToken = headers['x-auth-token']; if (!authToken) { throw new error_1.WebhookAuthorizationError(401); } const expectedAuthToken = generateBasicAuthToken(ctx.getNode(), expectedAuth); if (!expectedAuthToken || typeof authToken !== 'string' || expectedAuthToken.length !== authToken.length || !(0, node_crypto_1.timingSafeEqual)(Buffer.from(expectedAuthToken), Buffer.from(authToken))) { throw new error_1.WebhookAuthorizationError(403); } } else if (providedAuth.name !== expectedAuth.user || providedAuth.pass !== expectedAuth.password) { // Provided authentication data is wrong throw new error_1.WebhookAuthorizationError(403); } } else if (authentication === 'bearerAuth') { let expectedAuth; try { expectedAuth = await ctx.getCredentials('httpBearerAuth'); } catch { } const expectedToken = expectedAuth?.token; if (!expectedToken) { throw new error_1.WebhookAuthorizationError(500, 'No authentication data defined on node!'); } if (headers.authorization !== `Bearer ${expectedToken}`) { throw new error_1.WebhookAuthorizationError(403); } } else if (authentication === 'headerAuth') { // Special header with value is needed to call webhook let expectedAuth; try { expectedAuth = await ctx.getCredentials('httpHeaderAuth'); } catch { } if (expectedAuth === undefined || !expectedAuth.name || !expectedAuth.value) { // Data is not defined on node so can not authenticate throw new error_1.WebhookAuthorizationError(500, 'No authentication data defined on node!'); } const headerName = expectedAuth.name.toLowerCase(); const expectedValue = expectedAuth.value; if (!headers.hasOwnProperty(headerName) || headers[headerName] !== expectedValue) { // Provided authentication data is wrong throw new error_1.WebhookAuthorizationError(403); } } else if (authentication === 'jwtAuth') { let expectedAuth; try { expectedAuth = await ctx.getCredentials('jwtAuth'); } catch { } if (expectedAuth === undefined) { // Data is not defined on node so can not authenticate throw new error_1.WebhookAuthorizationError(500, 'No authentication data defined on node!'); } const authHeader = req.headers.authorization; const token = authHeader?.split(' ')[1]; if (!token) { throw new error_1.WebhookAuthorizationError(401, 'No token provided'); } let secretOrPublicKey; if (expectedAuth.keyType === 'passphrase') { secretOrPublicKey = expectedAuth.secret; } else { secretOrPublicKey = (0, utilities_1.formatPrivateKey)(expectedAuth.publicKey, true); } try { return jsonwebtoken_1.default.verify(token, secretOrPublicKey, { algorithms: [expectedAuth.algorithm], }); } catch (error) { throw new error_1.WebhookAuthorizationError(403, error.message); } } } async function handleFormData(context, prepareOutput) { const req = context.getRequestObject(); a.ok(req.contentType === 'multipart/form-data', 'Expected multipart/form-data'); const options = context.getNodeParameter('options', {}); const { data, files } = req.body; const returnItem = { json: { headers: req.headers, params: req.params, query: req.query, body: data, }, }; if (files && Object.keys(files).length) { returnItem.binary = {}; } let count = 0; for (const key of Object.keys(files)) { const processFiles = []; let multiFile = false; if (Array.isArray(files[key])) { processFiles.push.apply(processFiles, files[key]); multiFile = true; } else { processFiles.push(files[key]); } let fileCount = 0; for (const file of processFiles) { let binaryPropertyName = key; if (binaryPropertyName.endsWith('[]')) { binaryPropertyName = binaryPropertyName.slice(0, -2); } if (!binaryPropertyName.trim().length) { binaryPropertyName = `data${count}`; } else if (multiFile) { binaryPropertyName += fileCount++; } if (options.binaryPropertyName) { binaryPropertyName = `${options.binaryPropertyName}${count}`; } returnItem.binary[binaryPropertyName] = await context.nodeHelpers.copyBinaryFile(file.filepath, file.originalFilename ?? file.newFilename, file.mimetype); // Delete original file to prevent tmp directory from growing too large await (0, promises_1.rm)(file.filepath, { force: true }); count += 1; } } return { workflowData: prepareOutput(returnItem) }; } async function generateFormPostBasicAuthToken(context, authPropertyName) { const node = context.getNode(); const authentication = context.getNodeParameter(authPropertyName); if (authentication === 'none') return; let credentials; try { credentials = await context.getCredentials('httpBasicAuth'); } catch { } return generateBasicAuthToken(node, credentials); } function generateBasicAuthToken(node, credentials) { if (!credentials || !credentials.user || !credentials.password) { return; } const token = (0, node_crypto_1.createHmac)('sha256', `${credentials.user}:${credentials.password}`) .update(`${node.id}-${node.webhookId}`) .digest('hex'); return token; } //# sourceMappingURL=utils.js.map