diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index c7f7ea1d69..3e6fe50cb4 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -161,6 +161,24 @@ export class ActiveWorkflowRunner { }); } +/** + * Gets all request methods associated with a single webhook + * @param path webhook path + */ + async getWebhookMethods(path: string) : Promise { + const webhooks = await Db.collections.Webhook?.find({ webhookPath: path}) as IWebhookDb[]; + + // check if something exist + if (webhooks === undefined) { + // The requested webhooks are not registered + throw new ResponseHelper.ResponseError(`The requested webhook "${path}" is not registered.`, 404, 404); + } + + // Gather all request methods in string array + let webhookMethods : string[] = webhooks.map(webhook => webhook.method); + return webhookMethods; + } + /** * Returns the ids of the currently active workflows * diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index ee5bc80877..da8c7b602e 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -1567,6 +1567,25 @@ class App { ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); }); + // OPTIONS webhook requests + this.app.options(`/${this.endpointWebhook}/*`, async (req: express.Request, res: express.Response) => { + // Cut away the "/webhook/" to get the registred part of the url + const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhook.length + 2); + + let allowedMethods; + try { + allowedMethods = await this.activeWorkflowRunner.getWebhookMethods(requestUrl); + + // Add custom "Allow" header to satisfy OPTIONS response. + res.append('Allow', allowedMethods); + } catch (error) { + ResponseHelper.sendErrorResponse(res, error); + return; + } + + ResponseHelper.sendSuccessResponse(res, {}, true, 204); + }); + // GET webhook requests this.app.get(`/${this.endpointWebhook}/*`, async (req: express.Request, res: express.Response) => { // Cut away the "/webhook/" to get the registred part of the url @@ -1630,6 +1649,25 @@ class App { ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); }); + // HEAD webhook requests (test for UI) + this.app.options(`/${this.endpointWebhookTest}/*`, async (req: express.Request, res: express.Response) => { + // Cut away the "/webhook-test/" to get the registred part of the url + const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhookTest.length + 2); + + let allowedMethods; + try { + allowedMethods = await this.testWebhooks.getWebhookMethods(requestUrl); + + // Add custom "Allow" header to satisfy OPTIONS response. + res.append('Allow', allowedMethods); + } catch (error) { + ResponseHelper.sendErrorResponse(res, error); + return; + } + + ResponseHelper.sendSuccessResponse(res, {}, true, 204); + }); + // GET webhook requests (test for UI) this.app.get(`/${this.endpointWebhookTest}/*`, async (req: express.Request, res: express.Response) => { // Cut away the "/webhook-test/" to get the registred part of the url diff --git a/packages/cli/src/TestWebhooks.ts b/packages/cli/src/TestWebhooks.ts index a20fa76c88..1294258546 100644 --- a/packages/cli/src/TestWebhooks.ts +++ b/packages/cli/src/TestWebhooks.ts @@ -110,6 +110,21 @@ export class TestWebhooks { }); } + /** + * Gets all request methods associated with a single test webhook + * @param path webhook path + */ + async getWebhookMethods(path : string) : Promise { + const webhookMethods: string[] = this.activeWebhooks!.getWebhookMethods(path); + + if (webhookMethods === undefined) { + // The requested webhook is not registered + throw new ResponseHelper.ResponseError(`The requested webhook "${path}" is not registered.`, 404, 404); + } + + return webhookMethods; + } + /** * Checks if it has to wait for webhook data to execute the workflow. If yes it waits diff --git a/packages/core/src/ActiveWebhooks.ts b/packages/core/src/ActiveWebhooks.ts index 04b915d613..0e4ea24434 100644 --- a/packages/core/src/ActiveWebhooks.ts +++ b/packages/core/src/ActiveWebhooks.ts @@ -85,6 +85,21 @@ export class ActiveWebhooks { return this.webhookUrls[webhookKey]; } + /** + * Gets all request methods associated with a single webhook + * @param path + */ + getWebhookMethods(path: string): string[] { + let methods : string[] = []; + + Object.keys(this.webhookUrls) + .filter(key => key.includes(path)) + .map(key => { + methods.push(key.split('|')[0]); + }); + + return methods; + } /** * Returns the ids of all the workflows which have active webhooks diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index fa3628e379..0b6a7c79c3 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -590,7 +590,7 @@ export interface IWorkflowMetadata { active: boolean; } -export type WebhookHttpMethod = 'GET' | 'POST' | 'HEAD'; +export type WebhookHttpMethod = 'GET' | 'POST' | 'HEAD' | 'OPTIONS'; export interface IWebhookResponseData { workflowData?: INodeExecutionData[][];