From 7b73a8dbe784de4b6bad316eaa7f2f966798b161 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 3 Nov 2021 11:55:01 +0000 Subject: [PATCH 1/5] Fixing issue #3199 - don't allow submitting automation creation modal without name, adding error message. --- .../AutomationPanel/CreateAutomationModal.svelte | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte b/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte index f3273aa5ec..cf3dc8f314 100644 --- a/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte @@ -8,10 +8,13 @@ let name let selectedTrigger + let nameTouched = false let triggerVal export let webhookModal $: instanceId = $database._id + $: nameError = + nameTouched && !name ? "Please specify a name for the automation." : null async function createAutomation() { await automationStore.actions.create({ @@ -51,13 +54,18 @@ confirmText="Save" size="M" onConfirm={createAutomation} - disabled={!selectedTrigger} + disabled={!selectedTrigger || !name} > Please name your automation, then select a trigger. Every automation must start with a trigger. - + (nameTouched = true)} + bind:error={nameError} + label="Name" + /> Triggers From f8b2429bd0179dd47162a25f95c44a92e70dd77e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 3 Nov 2021 13:12:20 +0000 Subject: [PATCH 2/5] Fixing REST PUT using POST as per #3227. --- packages/server/src/api/controllers/query.js | 3 --- packages/server/src/integrations/rest.ts | 14 ++++++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index 89bf1a8ab0..a22cda4ebb 100644 --- a/packages/server/src/api/controllers/query.js +++ b/packages/server/src/api/controllers/query.js @@ -23,9 +23,6 @@ function formatResponse(resp) { try { resp = JSON.parse(resp) } catch (err) { - console.error( - "Error parsing JSON response. Returning string in array instead." - ) resp = { response: resp } } } diff --git a/packages/server/src/integrations/rest.ts b/packages/server/src/integrations/rest.ts index 9c6ece52d6..cf234518d9 100644 --- a/packages/server/src/integrations/rest.ts +++ b/packages/server/src/integrations/rest.ts @@ -142,13 +142,11 @@ module RestModule { } async parseResponse(response: any) { - switch (this.headers.Accept) { - case "application/json": - return await response.json() - case "text/html": - return await response.text() - default: - return await response.json() + const contentType = response.headers.get("content-type") + if (contentType && contentType.indexOf("application/json") !== -1) { + return await response.json() + } else { + return await response.text() } } @@ -191,7 +189,7 @@ module RestModule { } const response = await fetch(this.getUrl(path, queryString), { - method: "POST", + method: "PUT", headers: this.headers, body: JSON.stringify(json), }) From ea6646f055cb9f2a9c3c6ea0750cf96c6a9df52d Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 3 Nov 2021 13:13:22 +0000 Subject: [PATCH 3/5] Fixing #3237 and #3235 - always apply headers on out going webhooks if they are specified and handle a range of response codes. --- .../src/automations/steps/outgoingWebhook.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/server/src/automations/steps/outgoingWebhook.js b/packages/server/src/automations/steps/outgoingWebhook.js index 34299d23b6..f0637c3351 100644 --- a/packages/server/src/automations/steps/outgoingWebhook.js +++ b/packages/server/src/automations/steps/outgoingWebhook.js @@ -85,6 +85,18 @@ exports.run = async function ({ inputs }) { const request = { method: requestMethod, } + if (headers) { + try { + const customHeaders = + typeof headers === "string" ? JSON.parse(headers) : headers + request.headers = { ...request.headers, ...customHeaders } + } catch (err) { + return { + success: false, + response: "Unable to process headers, must be a JSON object.", + } + } + } if ( requestBody && requestBody.length !== 0 && @@ -95,21 +107,9 @@ exports.run = async function ({ inputs }) { ? requestBody : JSON.stringify(requestBody) request.headers = { + ...request.headers, "Content-Type": "application/json", } - - if (headers) { - try { - const customHeaders = - typeof headers === "string" ? JSON.parse(headers) : headers - request.headers = { ...request.headers, ...customHeaders } - } catch (err) { - return { - success: false, - response: "Unable to process headers, must be a JSON object.", - } - } - } } try { @@ -122,7 +122,7 @@ exports.run = async function ({ inputs }) { return { httpStatus: status, response: message, - success: status === 200, + success: status >= 200 && status <= 206, } } catch (err) { /* istanbul ignore next */ From 88a729913cacdf91acdda3d22a8c5b763c5c2841 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 3 Nov 2021 14:08:47 +0000 Subject: [PATCH 4/5] Fixing an issue with webhooks, couldn't use them in development (like getting schema) and making sure trigger will always use production app #3143. --- .../server/src/api/controllers/webhook.js | 49 ++++++++++++------- packages/server/src/automations/utils.js | 7 ++- packages/server/src/middleware/authorized.js | 7 +-- packages/server/src/middleware/currentapp.js | 2 + packages/server/src/middleware/utils.js | 7 +++ 5 files changed, 47 insertions(+), 25 deletions(-) create mode 100644 packages/server/src/middleware/utils.js diff --git a/packages/server/src/api/controllers/webhook.js b/packages/server/src/api/controllers/webhook.js index c810f85004..15ee748d8d 100644 --- a/packages/server/src/api/controllers/webhook.js +++ b/packages/server/src/api/controllers/webhook.js @@ -3,6 +3,7 @@ const { generateWebhookID, getWebhookParams } = require("../../db/utils") const toJsonSchema = require("to-json-schema") const validate = require("jsonschema").validate const triggers = require("../../automations/triggers") +const { getDeployedAppID } = require("@budibase/auth/db") const AUTOMATION_DESCRIPTION = "Generated from Webhook Schema" @@ -76,24 +77,34 @@ exports.buildSchema = async ctx => { } exports.trigger = async ctx => { - const db = new CouchDB(ctx.params.instance) - const webhook = await db.get(ctx.params.id) - // validate against the schema - if (webhook.bodySchema) { - validate(ctx.request.body, webhook.bodySchema) - } - const target = await db.get(webhook.action.target) - if (webhook.action.type === exports.WebhookType.AUTOMATION) { - // trigger with both the pure request and then expand it - // incase the user has produced a schema to bind to - await triggers.externalTrigger(target, { - body: ctx.request.body, - ...ctx.request.body, - appId: ctx.params.instance, - }) - } - ctx.status = 200 - ctx.body = { - message: "Webhook trigger fired successfully", + const prodAppId = getDeployedAppID(ctx.params.instance) + try { + const db = new CouchDB(prodAppId) + const webhook = await db.get(ctx.params.id) + // validate against the schema + if (webhook.bodySchema) { + validate(ctx.request.body, webhook.bodySchema) + } + const target = await db.get(webhook.action.target) + if (webhook.action.type === exports.WebhookType.AUTOMATION) { + // trigger with both the pure request and then expand it + // incase the user has produced a schema to bind to + await triggers.externalTrigger(target, { + body: ctx.request.body, + ...ctx.request.body, + appId: prodAppId, + }) + } + ctx.status = 200 + ctx.body = { + message: "Webhook trigger fired successfully", + } + } catch (err) { + if (err.status === 404) { + ctx.status = 200 + ctx.body = { + message: "Application not deployed yet.", + } + } } } diff --git a/packages/server/src/automations/utils.js b/packages/server/src/automations/utils.js index 4bef91a153..f2d1bf5699 100644 --- a/packages/server/src/automations/utils.js +++ b/packages/server/src/automations/utils.js @@ -6,6 +6,7 @@ const { queue } = require("./bullboard") const newid = require("../db/newid") const { updateEntityMetadata } = require("../utilities") const { MetadataTypes } = require("../constants") +const { getDeployedAppID } = require("@budibase/auth/db") const WH_STEP_ID = definitions.WEBHOOK.stepId const CRON_STEP_ID = definitions.CRON.stepId @@ -150,9 +151,13 @@ exports.checkForWebhooks = async ({ appId, oldAuto, newAuto }) => { await webhooks.save(ctx) const id = ctx.body.webhook._id newTrigger.webhookId = id + // the app ID has to be development for this endpoint + // it can only be used when building the app + // but the trigger endpoint will always be used in production + const prodAppId = getDeployedAppID(appId) newTrigger.inputs = { schemaUrl: `api/webhooks/schema/${appId}/${id}`, - triggerUrl: `api/webhooks/trigger/${appId}/${id}`, + triggerUrl: `api/webhooks/trigger/${prodAppId}/${id}`, } } return newAuto diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index bd064f7e66..d91311e165 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -5,20 +5,17 @@ const { doesHaveBasePermission, } = require("@budibase/auth/permissions") const builderMiddleware = require("./builder") +const { isWebhookEndpoint } = require("./utils") function hasResource(ctx) { return ctx.resourceId != null } -const WEBHOOK_ENDPOINTS = new RegExp( - ["webhooks/trigger", "webhooks/schema"].join("|") -) - module.exports = (permType, permLevel = null) => async (ctx, next) => { // webhooks don't need authentication, each webhook unique - if (WEBHOOK_ENDPOINTS.test(ctx.request.url)) { + if (isWebhookEndpoint(ctx)) { return next() } diff --git a/packages/server/src/middleware/currentapp.js b/packages/server/src/middleware/currentapp.js index 01b9dcc248..5682a860b9 100644 --- a/packages/server/src/middleware/currentapp.js +++ b/packages/server/src/middleware/currentapp.js @@ -9,6 +9,7 @@ const { isUserInAppTenant } = require("@budibase/auth/tenancy") const { getCachedSelf } = require("../utilities/global") const CouchDB = require("../db") const env = require("../environment") +const { isWebhookEndpoint } = require("./utils") module.exports = async (ctx, next) => { // try to get the appID from the request @@ -38,6 +39,7 @@ module.exports = async (ctx, next) => { // deny access to application preview if ( isDevAppID(requestAppId) && + !isWebhookEndpoint(ctx) && (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) ) { clearCookie(ctx, Cookies.CurrentApp) diff --git a/packages/server/src/middleware/utils.js b/packages/server/src/middleware/utils.js new file mode 100644 index 0000000000..b1eea8cd66 --- /dev/null +++ b/packages/server/src/middleware/utils.js @@ -0,0 +1,7 @@ +const WEBHOOK_ENDPOINTS = new RegExp( + ["webhooks/trigger", "webhooks/schema"].join("|") +) + +exports.isWebhookEndpoint = ctx => { + return WEBHOOK_ENDPOINTS.test(ctx.request.url) +} From b2bf5056b54f6213ca2ac2dbe13bc7e840d4d1b1 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 3 Nov 2021 15:45:19 +0000 Subject: [PATCH 5/5] Fixing rest test mocking. --- packages/server/src/integrations/tests/rest.spec.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/server/src/integrations/tests/rest.spec.js b/packages/server/src/integrations/tests/rest.spec.js index cb69d3c38d..7b128a6d14 100644 --- a/packages/server/src/integrations/tests/rest.spec.js +++ b/packages/server/src/integrations/tests/rest.spec.js @@ -1,5 +1,11 @@ jest.mock("node-fetch", () => - jest.fn(() => ({ json: jest.fn(), text: jest.fn() })) + jest.fn(() => ({ + headers: { + get: () => ["application/json"] + }, + json: jest.fn(), + text: jest.fn() + })) ) const fetch = require("node-fetch") const RestIntegration = require("../rest")