Merge branch 'develop' of github.com:Budibase/budibase into fix/user-metadata

This commit is contained in:
mike12345567 2021-11-08 17:28:32 +00:00
commit 99c47ab264
37 changed files with 1050 additions and 161 deletions

View File

@ -1,5 +1,5 @@
{ {
"version": "0.9.176-alpha.0", "version": "0.9.180-alpha.0",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/auth", "name": "@budibase/auth",
"version": "0.9.176-alpha.0", "version": "0.9.180-alpha.0",
"description": "Authentication middlewares for budibase builder and apps", "description": "Authentication middlewares for budibase builder and apps",
"main": "src/index.js", "main": "src/index.js",
"author": "Budibase", "author": "Budibase",

View File

@ -6,6 +6,7 @@ exports.UserStatus = {
exports.Cookies = { exports.Cookies = {
CurrentApp: "budibase:currentapp", CurrentApp: "budibase:currentapp",
Auth: "budibase:auth", Auth: "budibase:auth",
Init: "budibase:init",
OIDC_CONFIG: "budibase:oidc:config", OIDC_CONFIG: "budibase:oidc:config",
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "0.9.176-alpha.0", "version": "0.9.180-alpha.0",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "0.9.176-alpha.0", "version": "0.9.180-alpha.0",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -65,10 +65,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.176-alpha.0", "@budibase/bbui": "^0.9.180-alpha.0",
"@budibase/client": "^0.9.176-alpha.0", "@budibase/client": "^0.9.180-alpha.0",
"@budibase/colorpicker": "1.1.2", "@budibase/colorpicker": "1.1.2",
"@budibase/string-templates": "^0.9.176-alpha.0", "@budibase/string-templates": "^0.9.180-alpha.0",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View File

@ -8,10 +8,13 @@
let name let name
let selectedTrigger let selectedTrigger
let nameTouched = false
let triggerVal let triggerVal
export let webhookModal export let webhookModal
$: instanceId = $database._id $: instanceId = $database._id
$: nameError =
nameTouched && !name ? "Please specify a name for the automation." : null
async function createAutomation() { async function createAutomation() {
await automationStore.actions.create({ await automationStore.actions.create({
@ -51,13 +54,18 @@
confirmText="Save" confirmText="Save"
size="M" size="M"
onConfirm={createAutomation} onConfirm={createAutomation}
disabled={!selectedTrigger} disabled={!selectedTrigger || !name}
> >
<Body size="XS" <Body size="XS"
>Please name your automation, then select a trigger. Every automation must >Please name your automation, then select a trigger. Every automation must
start with a trigger. start with a trigger.
</Body> </Body>
<Input bind:value={name} label="Name" /> <Input
bind:value={name}
on:change={() => (nameTouched = true)}
bind:error={nameError}
label="Name"
/>
<Layout noPadding> <Layout noPadding>
<Body size="S">Triggers</Body> <Body size="S">Triggers</Body>

View File

@ -33,6 +33,16 @@
.instanceName("Content Placeholder") .instanceName("Content Placeholder")
.json() .json()
// Messages that can be sent from the iframe preview to the builder
// Budibase events are and initalisation events
const MessageTypes = {
IFRAME_LOADED: "iframe-loaded",
READY: "ready",
ERROR: "error",
BUDIBASE: "type",
KEYDOWN: "keydown"
}
// Construct iframe template // Construct iframe template
$: template = iframeTemplate.replace( $: template = iframeTemplate.replace(
/\{\{ CLIENT_LIB_PATH }}/, /\{\{ CLIENT_LIB_PATH }}/,
@ -80,46 +90,44 @@
// Refresh the preview when required // Refresh the preview when required
$: refreshContent(strippedJson) $: refreshContent(strippedJson)
onMount(() => { function receiveMessage(message) {
// Initialise the app when mounted const handlers = {
iframe.contentWindow.addEventListener( [MessageTypes.READY]: () => {
"ready", // Initialise the app when mounted
() => {
// Display preview immediately if the intelligent loading feature // Display preview immediately if the intelligent loading feature
// is not supported // is not supported
if (!loading) return
if (!$store.clientFeatures.intelligentLoading) { if (!$store.clientFeatures.intelligentLoading) {
loading = false loading = false
} }
refreshContent(strippedJson) refreshContent(strippedJson)
}, },
{ once: true } [MessageTypes.ERROR]: event => {
) // Catch any app errors
// Catch any app errors
iframe.contentWindow.addEventListener(
"error",
event => {
loading = false loading = false
error = event.detail || "An unknown error occurred" error = event.error || "An unknown error occurred"
}, },
{ once: true } [MessageTypes.KEYDOWN]: handleKeydownEvent
) }
// Add listener for events sent by client library in preview const messageHandler = handlers[message.data.type] || handleBudibaseEvent
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent) messageHandler(message)
iframe.contentWindow.addEventListener("keydown", handleKeydownEvent) }
onMount(() => {
window.addEventListener("message", receiveMessage)
}) })
// Remove all iframe event listeners on component destroy // Remove all iframe event listeners on component destroy
onDestroy(() => { onDestroy(() => {
if (iframe.contentWindow) { if (iframe.contentWindow) {
iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent) window.removeEventListener("message", receiveMessage) //
iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent)
} }
}) })
const handleBudibaseEvent = event => { const handleBudibaseEvent = event => {
const { type, data } = event.detail const { type, data } = event.data
if (type === "select-component" && data.id) { if (type === "select-component" && data.id) {
store.actions.components.select({ _id: data.id }) store.actions.components.select({ _id: data.id })
} else if (type === "update-prop") { } else if (type === "update-prop") {
@ -151,13 +159,14 @@
store.actions.components.paste(destination, data.mode) store.actions.components.paste(destination, data.mode)
} }
} else { } else {
console.warning(`Client sent unknown event type: ${type}`) console.warn(`Client sent unknown event type: ${type}`)
} }
} }
const handleKeydownEvent = event => { const handleKeydownEvent = event => {
const { key } = event.data
if ( if (
(event.key === "Delete" || event.key === "Backspace") && (key === "Delete" || key === "Backspace") &&
selectedComponentId && selectedComponentId &&
["input", "textarea"].indexOf( ["input", "textarea"].indexOf(
iframe.contentWindow.document.activeElement?.tagName.toLowerCase() iframe.contentWindow.document.activeElement?.tagName.toLowerCase()

View File

@ -84,17 +84,20 @@ export default `
if (window.loadBudibase) { if (window.loadBudibase) {
window.loadBudibase() window.loadBudibase()
document.documentElement.classList.add("loaded") document.documentElement.classList.add("loaded")
window.dispatchEvent(new Event("iframe-loaded")) window.parent.postMessage({ type: "iframe-loaded" })
} else { } else {
throw "The client library couldn't be loaded" throw "The client library couldn't be loaded"
} }
} catch (error) { } catch (error) {
window.dispatchEvent(new CustomEvent("error", { detail: error })) window.parent.postMessage({ type: "error", error })
} }
} }
window.addEventListener("message", receiveMessage) window.addEventListener("message", receiveMessage)
window.dispatchEvent(new Event("ready")) window.addEventListener("keydown", evt => {
window.parent.postMessage({ type: "keydown", key: event.key })
})
window.parent.postMessage({ type: "ready" })
</script> </script>
</head> </head>
<body/> <body/>

View File

@ -9,7 +9,7 @@
Checkbox, Checkbox,
} from "@budibase/bbui" } from "@budibase/bbui"
import { store, automationStore, hostingStore } from "builderStore" import { store, automationStore, hostingStore } from "builderStore"
import { admin } from "stores/portal" import { admin, auth } from "stores/portal"
import { string, mixed, object } from "yup" import { string, mixed, object } from "yup"
import api, { get, post } from "builderStore/api" import api, { get, post } from "builderStore/api"
import analytics, { Events } from "analytics" import analytics, { Events } from "analytics"
@ -139,6 +139,7 @@
} }
const userResp = await api.post(`/api/users/metadata/self`, user) const userResp = await api.post(`/api/users/metadata/self`, user)
await userResp.json() await userResp.json()
await auth.setInitInfo({})
$goto(`/builder/app/${appJson.instance._id}`) $goto(`/builder/app/${appJson.instance._id}`)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -146,6 +147,16 @@
submitting = false 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
}
</script> </script>
{#if showTemplateSelection} {#if showTemplateSelection}
@ -172,7 +183,7 @@
</ModalContent> </ModalContent>
{:else} {:else}
<ModalContent <ModalContent
title={template?.fromFile ? "Import app" : "Create app"} title={getModalTitle()}
confirmText={template?.fromFile ? "Import app" : "Create app"} confirmText={template?.fromFile ? "Import app" : "Create app"}
onConfirm={createNewApp} onConfirm={createNewApp}
onCancel={inline ? () => (template = null) : null} onCancel={inline ? () => (template = null) : null}

View File

@ -1,5 +1,5 @@
<script> <script>
import { isActive, redirect } from "@roxi/routify" import { isActive, redirect, params } from "@roxi/routify"
import { admin, auth } from "stores/portal" import { admin, auth } from "stores/portal"
import { onMount } from "svelte" import { onMount } from "svelte"
@ -47,6 +47,10 @@
} }
onMount(async () => { onMount(async () => {
if ($params["?template"]) {
await auth.setInitInfo({ init_template: $params["?template"] })
}
await auth.checkAuth() await auth.checkAuth()
await admin.init() await admin.init()

View File

@ -184,9 +184,27 @@
} }
} }
function createAppFromTemplateUrl(templateKey) {
// validate the template key just to make sure
const templateParts = templateKey.split("/")
if (templateParts.length === 2 && templateParts[0] === "app") {
template = {
key: templateKey,
}
initiateAppCreation()
} else {
notifications.error("Your Template URL is invalid. Please try another.")
}
}
onMount(async () => { onMount(async () => {
await apps.load() await apps.load()
loaded = true loaded = true
// if the portal is loaded from an external URL with a template param
const initInfo = await auth.getInitInfo()
if (initInfo.init_template) {
createAppFromTemplateUrl(initInfo.init_template)
}
}) })
</script> </script>

View File

@ -83,6 +83,13 @@ export function createAuthStore() {
return { return {
subscribe: store.subscribe, subscribe: store.subscribe,
setOrganisation: setOrganisation, 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 () => { checkQueryString: async () => {
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams(window.location.search)
if (urlParams.has("tenantId")) { if (urlParams.has("tenantId")) {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "0.9.176-alpha.0", "version": "0.9.180-alpha.0",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {

View File

@ -19,7 +19,7 @@ The object key is the name of the component, as exported by `index.js`.
- **bindable** - whether the components provides a bindable value or not - **bindable** - whether the components provides a bindable value or not
- **settings** - array of settings displayed in the builder - **settings** - array of settings displayed in the builder
###Settings Definitions ### Settings Definitions
The `type` field in each setting is used by the builder to know which component to use to display The `type` field in each setting is used by the builder to know which component to use to display
the setting, so it's important that this field is correct. The valid options are: the setting, so it's important that this field is correct. The valid options are:

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "0.9.176-alpha.0", "version": "0.9.180-alpha.0",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.176-alpha.0", "@budibase/bbui": "^0.9.180-alpha.0",
"@budibase/standard-components": "^0.9.139", "@budibase/standard-components": "^0.9.139",
"@budibase/string-templates": "^0.9.176-alpha.0", "@budibase/string-templates": "^0.9.180-alpha.0",
"regexparam": "^1.3.0", "regexparam": "^1.3.0",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"svelte-spa-router": "^3.0.5" "svelte-spa-router": "^3.0.5"

View File

@ -8,6 +8,12 @@
import { Modal, ModalContent, ActionButton } from "@budibase/bbui" import { Modal, ModalContent, ActionButton } from "@budibase/bbui"
import { onDestroy } from "svelte" import { onDestroy } from "svelte"
const MessageTypes = {
NOTIFICATION: "notification",
CLOSE_SCREEN_MODAL: "close-screen-modal",
INVALIDATE_DATASOURCE: "invalidate-datasource",
}
let iframe let iframe
let listenersAttached = false let listenersAttached = false
@ -21,32 +27,33 @@
notificationStore.actions.send(message, type, icon) notificationStore.actions.send(message, type, icon)
} }
function receiveMessage(message) {
const handlers = {
[MessageTypes.NOTIFICATION]: () => {
proxyNotification(message.data)
},
[MessageTypes.CLOSE_SCREEN_MODAL]: peekStore.actions.hidePeek,
[MessageTypes.INVALIDATE_DATASOURCE]: () => {
invalidateDataSource(message.data)
},
}
const messageHandler = handlers[message.data.type]
if (messageHandler) {
messageHandler(message)
} else {
console.warning("Unknown event type", message?.data?.type)
}
}
const attachListeners = () => { const attachListeners = () => {
// Mirror datasource invalidation to keep the parent window up to date // Mirror datasource invalidation to keep the parent window up to date
iframe.contentWindow.addEventListener( window.addEventListener("message", receiveMessage)
"invalidate-datasource",
invalidateDataSource
)
// Listen for a close event to close the screen peek
iframe.contentWindow.addEventListener(
"close-screen-modal",
peekStore.actions.hidePeek
)
// Proxy notifications back to the parent window instead of iframe
iframe.contentWindow.addEventListener("notification", proxyNotification)
} }
const handleCancel = () => { const handleCancel = () => {
peekStore.actions.hidePeek() peekStore.actions.hidePeek()
iframe.contentWindow.removeEventListener( window.removeEventListener("message", receiveMessage)
"invalidate-datasource",
invalidateDataSource
)
iframe.contentWindow.removeEventListener(
"close-screen-modal",
peekStore.actions.hidePeek
)
iframe.contentWindow.removeEventListener("notification", proxyNotification)
} }
const handleFullscreen = () => { const handleFullscreen = () => {

View File

@ -4,11 +4,7 @@ import { findComponentById, findComponentPathById } from "../utils/components"
import { pingEndUser } from "../api" import { pingEndUser } from "../api"
const dispatchEvent = (type, data = {}) => { const dispatchEvent = (type, data = {}) => {
window.dispatchEvent( window.parent.postMessage({ type, data })
new CustomEvent("bb-event", {
detail: { type, data },
})
)
} }
const createBuilderStore = () => { const createBuilderStore = () => {

View File

@ -59,11 +59,10 @@ export const createDataSourceStore = () => {
// Emit this as a window event, so parent screens which are iframing us in // Emit this as a window event, so parent screens which are iframing us in
// can also invalidate the same datasource // can also invalidate the same datasource
window.dispatchEvent( window.parent.postMessage({
new CustomEvent("invalidate-datasource", { type: "close-screen-modal",
detail: { dataSourceId }, detail: { dataSourceId },
}) })
)
let invalidations = [dataSourceId] let invalidations = [dataSourceId]

View File

@ -26,11 +26,14 @@ const createNotificationStore = () => {
// If peeking, pass notifications back to parent window // If peeking, pass notifications back to parent window
if (get(routeStore).queryParams?.peek) { if (get(routeStore).queryParams?.peek) {
window.dispatchEvent( window.parent.postMessage({
new CustomEvent("notification", { type: "notification",
detail: { message, type, icon }, detail: {
}) message,
) type,
icon,
},
})
return return
} }

View File

@ -120,7 +120,7 @@ const changeFormStepHandler = async (action, context) => {
const closeScreenModalHandler = () => { const closeScreenModalHandler = () => {
// Emit this as a window event, so parent screens which are iframing us in // Emit this as a window event, so parent screens which are iframing us in
// can close the modal // can close the modal
window.dispatchEvent(new Event("close-screen-modal")) window.parent.postMessage({ type: "close-screen-modal" })
} }
const updateStateHandler = action => { const updateStateHandler = action => {

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.176-alpha.0", "version": "0.9.180-alpha.0",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -68,9 +68,9 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.176-alpha.0", "@budibase/auth": "^0.9.180-alpha.0",
"@budibase/client": "^0.9.176-alpha.0", "@budibase/client": "^0.9.180-alpha.0",
"@budibase/string-templates": "^0.9.176-alpha.0", "@budibase/string-templates": "^0.9.180-alpha.0",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",
"@koa/router": "8.0.0", "@koa/router": "8.0.0",
"@sendgrid/mail": "7.1.1", "@sendgrid/mail": "7.1.1",

View File

@ -23,9 +23,6 @@ function formatResponse(resp) {
try { try {
resp = JSON.parse(resp) resp = JSON.parse(resp)
} catch (err) { } catch (err) {
console.error(
"Error parsing JSON response. Returning string in array instead."
)
resp = { response: resp } resp = { response: resp }
} }
} }

View File

@ -3,6 +3,7 @@ const { generateWebhookID, getWebhookParams } = require("../../db/utils")
const toJsonSchema = require("to-json-schema") const toJsonSchema = require("to-json-schema")
const validate = require("jsonschema").validate const validate = require("jsonschema").validate
const triggers = require("../../automations/triggers") const triggers = require("../../automations/triggers")
const { getDeployedAppID } = require("@budibase/auth/db")
const AUTOMATION_DESCRIPTION = "Generated from Webhook Schema" const AUTOMATION_DESCRIPTION = "Generated from Webhook Schema"
@ -76,24 +77,34 @@ exports.buildSchema = async ctx => {
} }
exports.trigger = async ctx => { exports.trigger = async ctx => {
const db = new CouchDB(ctx.params.instance) const prodAppId = getDeployedAppID(ctx.params.instance)
const webhook = await db.get(ctx.params.id) try {
// validate against the schema const db = new CouchDB(prodAppId)
if (webhook.bodySchema) { const webhook = await db.get(ctx.params.id)
validate(ctx.request.body, webhook.bodySchema) // validate against the schema
} if (webhook.bodySchema) {
const target = await db.get(webhook.action.target) validate(ctx.request.body, webhook.bodySchema)
if (webhook.action.type === exports.WebhookType.AUTOMATION) { }
// trigger with both the pure request and then expand it const target = await db.get(webhook.action.target)
// incase the user has produced a schema to bind to if (webhook.action.type === exports.WebhookType.AUTOMATION) {
await triggers.externalTrigger(target, { // trigger with both the pure request and then expand it
body: ctx.request.body, // incase the user has produced a schema to bind to
...ctx.request.body, await triggers.externalTrigger(target, {
appId: ctx.params.instance, body: ctx.request.body,
}) ...ctx.request.body,
} appId: prodAppId,
ctx.status = 200 })
ctx.body = { }
message: "Webhook trigger fired successfully", 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.",
}
}
} }
} }

View File

@ -85,6 +85,18 @@ exports.run = async function ({ inputs }) {
const request = { const request = {
method: requestMethod, 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 ( if (
requestBody && requestBody &&
requestBody.length !== 0 && requestBody.length !== 0 &&
@ -95,21 +107,9 @@ exports.run = async function ({ inputs }) {
? requestBody ? requestBody
: JSON.stringify(requestBody) : JSON.stringify(requestBody)
request.headers = { request.headers = {
...request.headers,
"Content-Type": "application/json", "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 { try {
@ -122,7 +122,7 @@ exports.run = async function ({ inputs }) {
return { return {
httpStatus: status, httpStatus: status,
response: message, response: message,
success: status === 200, success: status >= 200 && status <= 206,
} }
} catch (err) { } catch (err) {
/* istanbul ignore next */ /* istanbul ignore next */

View File

@ -6,6 +6,7 @@ const { queue } = require("./bullboard")
const newid = require("../db/newid") const newid = require("../db/newid")
const { updateEntityMetadata } = require("../utilities") const { updateEntityMetadata } = require("../utilities")
const { MetadataTypes } = require("../constants") const { MetadataTypes } = require("../constants")
const { getDeployedAppID } = require("@budibase/auth/db")
const WH_STEP_ID = definitions.WEBHOOK.stepId const WH_STEP_ID = definitions.WEBHOOK.stepId
const CRON_STEP_ID = definitions.CRON.stepId const CRON_STEP_ID = definitions.CRON.stepId
@ -150,9 +151,13 @@ exports.checkForWebhooks = async ({ appId, oldAuto, newAuto }) => {
await webhooks.save(ctx) await webhooks.save(ctx)
const id = ctx.body.webhook._id const id = ctx.body.webhook._id
newTrigger.webhookId = 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 = { newTrigger.inputs = {
schemaUrl: `api/webhooks/schema/${appId}/${id}`, schemaUrl: `api/webhooks/schema/${appId}/${id}`,
triggerUrl: `api/webhooks/trigger/${appId}/${id}`, triggerUrl: `api/webhooks/trigger/${prodAppId}/${id}`,
} }
} }
return newAuto return newAuto

View File

@ -142,13 +142,11 @@ module RestModule {
} }
async parseResponse(response: any) { async parseResponse(response: any) {
switch (this.headers.Accept) { const contentType = response.headers.get("content-type")
case "application/json": if (contentType && contentType.indexOf("application/json") !== -1) {
return await response.json() return await response.json()
case "text/html": } else {
return await response.text() return await response.text()
default:
return await response.json()
} }
} }
@ -191,7 +189,7 @@ module RestModule {
} }
const response = await fetch(this.getUrl(path, queryString), { const response = await fetch(this.getUrl(path, queryString), {
method: "POST", method: "PUT",
headers: this.headers, headers: this.headers,
body: JSON.stringify(json), body: JSON.stringify(json),
}) })

View File

@ -1,5 +1,11 @@
jest.mock("node-fetch", () => 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 fetch = require("node-fetch")
const RestIntegration = require("../rest") const RestIntegration = require("../rest")

View File

@ -124,7 +124,10 @@ function copyExistingPropsOver(
return table return table
} }
export function finaliseExternalTables(tables: { [key: string]: any }, entities: { [key: string]: any }) { export function finaliseExternalTables(
tables: { [key: string]: any },
entities: { [key: string]: any }
) {
const finalTables: { [key: string]: any } = {} const finalTables: { [key: string]: any } = {}
const errors: { [key: string]: string } = {} const errors: { [key: string]: string } = {}
for (let [name, table] of Object.entries(tables)) { for (let [name, table] of Object.entries(tables)) {

View File

@ -5,21 +5,18 @@ const {
doesHaveBasePermission, doesHaveBasePermission,
} = require("@budibase/auth/permissions") } = require("@budibase/auth/permissions")
const builderMiddleware = require("./builder") const builderMiddleware = require("./builder")
const { isWebhookEndpoint } = require("./utils")
function hasResource(ctx) { function hasResource(ctx) {
return ctx.resourceId != null return ctx.resourceId != null
} }
const WEBHOOK_ENDPOINTS = new RegExp(
["webhooks/trigger", "webhooks/schema"].join("|")
)
module.exports = module.exports =
(permType, permLevel = null) => (permType, permLevel = null) =>
async (ctx, next) => { async (ctx, next) => {
// webhooks don't need authentication, each webhook unique // webhooks don't need authentication, each webhook unique
// also internal requests (between services) don't need authorized // also internal requests (between services) don't need authorized
if (WEBHOOK_ENDPOINTS.test(ctx.request.url) || ctx.internal) { if (isWebhookEndpoint(ctx) || ctx.internal) {
return next() return next()
} }

View File

@ -9,6 +9,7 @@ const { isUserInAppTenant } = require("@budibase/auth/tenancy")
const { getCachedSelf } = require("../utilities/global") const { getCachedSelf } = require("../utilities/global")
const CouchDB = require("../db") const CouchDB = require("../db")
const env = require("../environment") const env = require("../environment")
const { isWebhookEndpoint } = require("./utils")
module.exports = async (ctx, next) => { module.exports = async (ctx, next) => {
// try to get the appID from the request // try to get the appID from the request
@ -38,6 +39,7 @@ module.exports = async (ctx, next) => {
// deny access to application preview // deny access to application preview
if ( if (
isDevAppID(requestAppId) && isDevAppID(requestAppId) &&
!isWebhookEndpoint(ctx) &&
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global) (!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
) { ) {
clearCookie(ctx, Cookies.CurrentApp) clearCookie(ctx, Cookies.CurrentApp)

View File

@ -2,6 +2,10 @@ const CouchDB = require("../db")
const usageQuota = require("../utilities/usageQuota") const usageQuota = require("../utilities/usageQuota")
const env = require("../environment") const env = require("../environment")
const { getTenantId } = require("@budibase/auth/tenancy") const { getTenantId } = require("@budibase/auth/tenancy")
const {
isExternalTable,
isRowId: isExternalRowId,
} = require("../integrations/utils")
// tenants without limits // tenants without limits
const EXCLUDED_TENANTS = ["bb", "default", "bbtest", "bbstaging"] 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 // post request could be a save of a pre-existing entry
if (ctx.request.body && ctx.request.body._id && ctx.request.body._rev) { if (ctx.request.body && ctx.request.body._id && ctx.request.body._rev) {
const usageId = ctx.request.body._id
try { try {
if (ctx.appId) { if (ctx.appId) {
const db = new CouchDB(ctx.appId) const db = new CouchDB(ctx.appId)
await db.get(ctx.request.body._id) await db.get(usageId)
} }
return next() return next()
} catch (err) { } 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`)
}
} }
} }

View File

@ -0,0 +1,7 @@
const WEBHOOK_ENDPOINTS = new RegExp(
["webhooks/trigger", "webhooks/schema"].join("|")
)
exports.isWebhookEndpoint = ctx => {
return WEBHOOK_ENDPOINTS.test(ctx.request.url)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "0.9.176-alpha.0", "version": "0.9.180-alpha.0",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.176-alpha.0", "version": "0.9.180-alpha.0",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -29,8 +29,8 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.176-alpha.0", "@budibase/auth": "^0.9.180-alpha.0",
"@budibase/string-templates": "^0.9.176-alpha.0", "@budibase/string-templates": "^0.9.180-alpha.0",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@sentry/node": "^6.0.0", "@sentry/node": "^6.0.0",
"@techpass/passport-openidconnect": "^0.3.0", "@techpass/passport-openidconnect": "^0.3.0",

View File

@ -77,6 +77,17 @@ exports.authenticate = async (ctx, next) => {
})(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. * Reset the user password, used as part of a forgotten password flow.
*/ */

View File

@ -56,6 +56,8 @@ router
authController.resetUpdate authController.resetUpdate
) )
.post("/api/global/auth/logout", authController.logout) .post("/api/global/auth/logout", authController.logout)
.post("/api/global/auth/init", authController.setInitInfo)
.get("/api/global/auth/init", authController.getInitInfo)
.get( .get(
"/api/global/auth/:tenantId/google", "/api/global/auth/:tenantId/google",
updateTenant, updateTenant,