diff --git a/lerna.json b/lerna.json index 18f9328afd..2b7b35e6fd 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.176-alpha.3", + "version": "0.9.180-alpha.0", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index b5f27ca059..91305f3cb9 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.176-alpha.3", + "version": "0.9.180-alpha.0", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/auth/src/constants.js b/packages/auth/src/constants.js index 4b4aef5a42..9892275bec 100644 --- a/packages/auth/src/constants.js +++ b/packages/auth/src/constants.js @@ -6,6 +6,7 @@ exports.UserStatus = { exports.Cookies = { CurrentApp: "budibase:currentapp", Auth: "budibase:auth", + Init: "budibase:init", OIDC_CONFIG: "budibase:oidc:config", } diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 0d1c5c15bc..ba325bcd0b 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.176-alpha.3", + "version": "0.9.180-alpha.0", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index 56811c6d11..c362ce6912 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.176-alpha.3", + "version": "0.9.180-alpha.0", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.176-alpha.3", - "@budibase/client": "^0.9.176-alpha.3", + "@budibase/bbui": "^0.9.180-alpha.0", + "@budibase/client": "^0.9.180-alpha.0", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.176-alpha.3", + "@budibase/string-templates": "^0.9.180-alpha.0", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", 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 diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index 2c8ae25abe..1db5e46261 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -9,7 +9,7 @@ Checkbox, } from "@budibase/bbui" import { store, automationStore, hostingStore } from "builderStore" - import { admin } from "stores/portal" + import { admin, auth } from "stores/portal" import { string, mixed, object } from "yup" import api, { get, post } from "builderStore/api" import analytics, { Events } from "analytics" @@ -139,6 +139,7 @@ } const userResp = await api.post(`/api/users/metadata/self`, user) await userResp.json() + await auth.setInitInfo({}) $goto(`/builder/app/${appJson.instance._id}`) } catch (error) { console.error(error) @@ -146,6 +147,16 @@ submitting = false } } + + function getModalTitle() { + let title = "Create App" + if (template.fromFile) { + title = "Import App" + } else if (template.key) { + title = "Create app from template" + } + return title + } {#if showTemplateSelection} @@ -172,7 +183,7 @@ {:else} (template = null) : null} diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index 39c93f0fb0..179131107f 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -1,5 +1,5 @@ diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index 134232dd74..bd08611fc9 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -83,6 +83,13 @@ export function createAuthStore() { return { subscribe: store.subscribe, setOrganisation: setOrganisation, + getInitInfo: async () => { + const response = await api.get(`/api/global/auth/init`) + return await response.json() + }, + setInitInfo: async info => { + await api.post(`/api/global/auth/init`, info) + }, checkQueryString: async () => { const urlParams = new URLSearchParams(window.location.search) if (urlParams.has("tenantId")) { diff --git a/packages/cli/package.json b/packages/cli/package.json index f6f873edc1..02c0c8e08e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.176-alpha.3", + "version": "0.9.180-alpha.0", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index dc487f6bf5..c925087740 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.176-alpha.3", + "version": "0.9.180-alpha.0", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.176-alpha.3", + "@budibase/bbui": "^0.9.180-alpha.0", "@budibase/standard-components": "^0.9.139", - "@budibase/string-templates": "^0.9.176-alpha.3", + "@budibase/string-templates": "^0.9.180-alpha.0", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/client/src/stores/dataSource.js b/packages/client/src/stores/dataSource.js index c60039aa8a..247ba18835 100644 --- a/packages/client/src/stores/dataSource.js +++ b/packages/client/src/stores/dataSource.js @@ -59,11 +59,10 @@ export const createDataSourceStore = () => { // Emit this as a window event, so parent screens which are iframing us in // can also invalidate the same datasource - window.dispatchEvent( - new CustomEvent("invalidate-datasource", { - detail: { dataSourceId }, - }) - ) + window.parent.postMessage({ + type: "close-screen-modal", + detail: { dataSourceId }, + }) let invalidations = [dataSourceId] diff --git a/packages/client/src/stores/notification.js b/packages/client/src/stores/notification.js index 97193b2092..64178328c0 100644 --- a/packages/client/src/stores/notification.js +++ b/packages/client/src/stores/notification.js @@ -34,11 +34,6 @@ const createNotificationStore = () => { icon, }, }) - // window.dispatchEvent( - // new CustomEvent("notification", { - // detail: { message, type, icon }, - // }) - // ) return } diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index 11aa033c1d..1fb2284375 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -120,7 +120,7 @@ const changeFormStepHandler = async (action, context) => { const closeScreenModalHandler = () => { // Emit this as a window event, so parent screens which are iframing us in // can close the modal - window.dispatchEvent(new Event("close-screen-modal")) + window.parent.postMessage({ type: "close-screen-modal" }) } const updateStateHandler = action => { diff --git a/packages/server/package.json b/packages/server/package.json index cee6a2a2e8..27c274f2d4 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.176-alpha.3", + "version": "0.9.180-alpha.0", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -68,9 +68,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.176-alpha.3", - "@budibase/client": "^0.9.176-alpha.3", - "@budibase/string-templates": "^0.9.176-alpha.3", + "@budibase/auth": "^0.9.180-alpha.0", + "@budibase/client": "^0.9.180-alpha.0", + "@budibase/string-templates": "^0.9.180-alpha.0", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index 5f9fadf99b..4383ff2910 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/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/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 */ 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/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), }) 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") 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/usageQuota.js b/packages/server/src/middleware/usageQuota.js index c62f0078cd..2b189b8660 100644 --- a/packages/server/src/middleware/usageQuota.js +++ b/packages/server/src/middleware/usageQuota.js @@ -2,6 +2,10 @@ const CouchDB = require("../db") const usageQuota = require("../utilities/usageQuota") const env = require("../environment") const { getTenantId } = require("@budibase/auth/tenancy") +const { + isExternalTable, + isRowId: isExternalRowId, +} = require("../integrations/utils") // tenants without limits const EXCLUDED_TENANTS = ["bb", "default", "bbtest", "bbstaging"] @@ -46,14 +50,24 @@ module.exports = async (ctx, next) => { } // post request could be a save of a pre-existing entry if (ctx.request.body && ctx.request.body._id && ctx.request.body._rev) { + const usageId = ctx.request.body._id try { if (ctx.appId) { const db = new CouchDB(ctx.appId) - await db.get(ctx.request.body._id) + await db.get(usageId) } return next() } catch (err) { - ctx.throw(404, `${ctx.request.body._id} does not exist`) + if ( + isExternalTable(usageId) || + (ctx.request.body.tableId && + isExternalTable(ctx.request.body.tableId)) || + isExternalRowId(usageId) + ) { + return next() + } else { + ctx.throw(404, `${usageId} does not exist`) + } } } 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) +} diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 3a7268920f..187f720407 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.176-alpha.3", + "version": "0.9.180-alpha.0", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index 8d1c7141b4..84650b7713 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.176-alpha.3", + "version": "0.9.180-alpha.0", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -29,8 +29,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.176-alpha.3", - "@budibase/string-templates": "^0.9.176-alpha.3", + "@budibase/auth": "^0.9.180-alpha.0", + "@budibase/string-templates": "^0.9.180-alpha.0", "@koa/router": "^8.0.0", "@sentry/node": "^6.0.0", "@techpass/passport-openidconnect": "^0.3.0", diff --git a/packages/worker/src/api/controllers/global/auth.js b/packages/worker/src/api/controllers/global/auth.js index 50b4ec969b..e111619041 100644 --- a/packages/worker/src/api/controllers/global/auth.js +++ b/packages/worker/src/api/controllers/global/auth.js @@ -77,6 +77,17 @@ exports.authenticate = async (ctx, next) => { })(ctx, next) } +exports.setInitInfo = ctx => { + const initInfo = ctx.request.body + setCookie(ctx, initInfo, Cookies.Init) + ctx.status = 200 +} + +exports.getInitInfo = ctx => { + const initInfo = getCookie(ctx, Cookies.Init) + ctx.body = initInfo +} + /** * Reset the user password, used as part of a forgotten password flow. */ diff --git a/packages/worker/src/api/routes/global/auth.js b/packages/worker/src/api/routes/global/auth.js index 9cd77ce153..7baad60ecd 100644 --- a/packages/worker/src/api/routes/global/auth.js +++ b/packages/worker/src/api/routes/global/auth.js @@ -56,6 +56,8 @@ router authController.resetUpdate ) .post("/api/global/auth/logout", authController.logout) + .post("/api/global/auth/init", authController.setInitInfo) + .get("/api/global/auth/init", authController.getInitInfo) .get( "/api/global/auth/:tenantId/google", updateTenant,