diff --git a/packages/cli/src/TestWebhooks.ts b/packages/cli/src/TestWebhooks.ts index d3db5357a4308..827226ee546be 100644 --- a/packages/cli/src/TestWebhooks.ts +++ b/packages/cli/src/TestWebhooks.ts @@ -238,13 +238,20 @@ export class TestWebhooks implements IWebhookManager { for (const webhook of webhooks) { const key = this.registrations.toKey(webhook); - const isAlreadyRegistered = await this.registrations.get(key); + const registrationByKey = await this.registrations.get(key); if (runData && webhook.node in runData) { return false; } - if (isAlreadyRegistered && !webhook.webhookId) { + // if registration already exists and is not a test webhook created by this user in this workflow throw an error + if ( + registrationByKey && + !webhook.webhookId && + !registrationByKey.webhook.isTest && + registrationByKey.webhook.userId !== userId && + registrationByKey.webhook.workflowId !== workflow.id + ) { throw new WebhookPathTakenError(webhook.node); } diff --git a/packages/editor-ui/src/components/NodeWebhooks.vue b/packages/editor-ui/src/components/NodeWebhooks.vue index 7f107f81dd135..5ee9d9979fb0a 100644 --- a/packages/editor-ui/src/components/NodeWebhooks.vue +++ b/packages/editor-ui/src/components/NodeWebhooks.vue @@ -28,9 +28,7 @@ >
-
- {{ workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod') }}
-
+
{{ getWebhookHttpMethod(webhook) }}
@@ -195,12 +193,27 @@ export default defineComponent({ return ''; }, isWebhookMethodVisible(webhook: IWebhookDescription): boolean { + try { + const method = this.workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod', false); + if (Array.isArray(method) && method.length !== 1) { + return false; + } + } catch (error) {} + if (typeof webhook.ndvHideMethod === 'string') { return !this.workflowHelpers.getWebhookExpressionValue(webhook, 'ndvHideMethod'); } return !webhook.ndvHideMethod; }, + + getWebhookHttpMethod(webhook: IWebhookDescription): string { + const method = this.workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod', false); + if (Array.isArray(method)) { + return method[0]; + } + return method; + }, }, }); diff --git a/packages/editor-ui/src/components/TriggerPanel.vue b/packages/editor-ui/src/components/TriggerPanel.vue index 8c022a924fbd5..c53ecfc0deda1 100644 --- a/packages/editor-ui/src/components/TriggerPanel.vue +++ b/packages/editor-ui/src/components/TriggerPanel.vue @@ -225,10 +225,17 @@ export default defineComponent({ return undefined; } - return this.workflowHelpers.getWebhookExpressionValue( + const httpMethod = this.workflowHelpers.getWebhookExpressionValue( this.nodeType.webhooks[0], 'httpMethod', + false, ); + + if (Array.isArray(httpMethod)) { + return httpMethod.join(', '); + } + + return httpMethod; }, webhookTestUrl(): string | undefined { if (!this.node || !this.nodeType?.webhooks?.length) { diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index 901f6bb122211..6fe5a9490c8f0 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -737,12 +737,21 @@ export function useWorkflowHelpers(options: { router: ReturnType unknown } | null; if (proxy?.isProxy && proxy.toJSON) return JSON.stringify(proxy.toJSON()); const workflow = getCurrentWorkflow(); diff --git a/packages/nodes-base/nodes/Webhook/Webhook.node.ts b/packages/nodes-base/nodes/Webhook/Webhook.node.ts index 31a877941a165..98b699962ac04 100644 --- a/packages/nodes-base/nodes/Webhook/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook/Webhook.node.ts @@ -74,7 +74,60 @@ export class Webhook extends Node { credentials: credentialsProperty(this.authPropertyName), webhooks: [defaultWebhookDescription], properties: [ - httpMethodsProperty, + { + displayName: 'Allow Multiple HTTP Methods', + name: 'multipleMethods', + type: 'boolean', + default: false, + isNodeSetting: true, + description: 'Whether to allow the webhook to listen for multiple HTTP methods', + }, + { + ...httpMethodsProperty, + displayOptions: { + show: { + multipleMethods: [false], + }, + }, + }, + { + displayName: 'HTTP Methods', + name: 'httpMethod', + type: 'multiOptions', + options: [ + { + name: 'DELETE', + value: 'DELETE', + }, + { + name: 'GET', + value: 'GET', + }, + { + name: 'HEAD', + value: 'HEAD', + }, + { + name: 'PATCH', + value: 'PATCH', + }, + { + name: 'POST', + value: 'POST', + }, + { + name: 'PUT', + value: 'PUT', + }, + ], + default: ['GET', 'POST'], + description: 'The HTTP methods to listen to', + displayOptions: { + show: { + multipleMethods: [true], + }, + }, + }, { displayName: 'Path', name: 'path', @@ -144,6 +197,7 @@ export class Webhook extends Node { }; const req = context.getRequestObject(); const resp = context.getResponseObject(); + const requestMethod = context.getRequestObject().method; if (!isIpWhitelisted(options.ipWhitelist, req.ips, req.ip)) { resp.writeHead(403); @@ -165,7 +219,7 @@ export class Webhook extends Node { throw error; } - const prepareOutput = setupOutputConnection(context, { + const prepareOutput = setupOutputConnection(context, requestMethod, { jwtPayload: validationData, }); diff --git a/packages/nodes-base/nodes/Webhook/utils.ts b/packages/nodes-base/nodes/Webhook/utils.ts index 1bf6b8407e451..33fd36331ecc8 100644 --- a/packages/nodes-base/nodes/Webhook/utils.ts +++ b/packages/nodes-base/nodes/Webhook/utils.ts @@ -50,22 +50,34 @@ export const getResponseData = (parameters: WebhookParameters) => { }; export const configuredOutputs = (parameters: WebhookParameters) => { - const httpMethod = parameters.httpMethod; + const httpMethod = parameters.httpMethod as string | string[]; - return [ - { + if (!Array.isArray(httpMethod)) + return [ + { + type: `${NodeConnectionType.Main}`, + displayName: httpMethod, + }, + ]; + + const outputs = httpMethod.map((method) => { + return { type: `${NodeConnectionType.Main}`, - displayName: httpMethod, - }, - ]; + displayName: method, + }; + }); + + return outputs; }; export const setupOutputConnection = ( ctx: IWebhookFunctions, + method: string, additionalData: { jwtPayload?: IDataObject; }, ) => { + const httpMethod = ctx.getNodeParameter('httpMethod', []) as string[] | string; let webhookUrl = ctx.getNodeWebhookUrl('default') as string; const executionMode = ctx.getMode() === 'manual' ? 'test' : 'production'; @@ -73,13 +85,29 @@ export const setupOutputConnection = ( 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: INodeExecutionData): INodeExecutionData[][] => { + 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: INodeExecutionData[][] = httpMethod.map(() => []); + return (outputData: INodeExecutionData): INodeExecutionData[][] => { outputData.json.webhookUrl = webhookUrl; outputData.json.executionMode = executionMode; if (additionalData?.jwtPayload) { outputData.json.jwtPayload = additionalData.jwtPayload; } - return [[outputData]]; + outputs[outputIndex] = [outputData]; + return outputs; }; }; diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index 8a097c2a9122b..06a405f9e8916 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -996,7 +996,7 @@ export function getNodeWebhooks( ) as boolean; const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath, restartWebhook); - const httpMethod = workflow.expression.getSimpleParameterValue( + const webhookMethods = workflow.expression.getSimpleParameterValue( node, webhookDescription.httpMethod, mode, @@ -1005,7 +1005,7 @@ export function getNodeWebhooks( 'GET', ); - if (httpMethod === undefined) { + if (webhookMethods === undefined) { // TODO: Use a proper logger console.error( `The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`, @@ -1018,15 +1018,20 @@ export function getNodeWebhooks( webhookId = node.webhookId; } - returnData.push({ - httpMethod: httpMethod.toString() as IHttpRequestMethods, - node: node.name, - path, - webhookDescription, - workflowId, - workflowExecuteAdditionalData: additionalData, - webhookId, - }); + String(webhookMethods) + .split(',') + .forEach((httpMethod) => { + if (!httpMethod) return; + returnData.push({ + httpMethod: httpMethod.trim() as IHttpRequestMethods, + node: node.name, + path, + webhookDescription, + workflowId, + workflowExecuteAdditionalData: additionalData, + webhookId, + }); + }); } return returnData;