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",
"packages": [
"packages/*"

View File

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

View File

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

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"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",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "0.9.176-alpha.0",
"version": "0.9.180-alpha.0",
"license": "AGPL-3.0",
"private": true,
"scripts": {
@ -65,10 +65,10 @@
}
},
"dependencies": {
"@budibase/bbui": "^0.9.176-alpha.0",
"@budibase/client": "^0.9.176-alpha.0",
"@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.0",
"@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",

View File

@ -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}
>
<Body size="XS"
>Please name your automation, then select a trigger. Every automation must
start with a trigger.
</Body>
<Input bind:value={name} label="Name" />
<Input
bind:value={name}
on:change={() => (nameTouched = true)}
bind:error={nameError}
label="Name"
/>
<Layout noPadding>
<Body size="S">Triggers</Body>

View File

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

View File

@ -84,17 +84,20 @@ export default `
if (window.loadBudibase) {
window.loadBudibase()
document.documentElement.classList.add("loaded")
window.dispatchEvent(new Event("iframe-loaded"))
window.parent.postMessage({ type: "iframe-loaded" })
} else {
throw "The client library couldn't be loaded"
}
} catch (error) {
window.dispatchEvent(new CustomEvent("error", { detail: error }))
window.parent.postMessage({ type: "error", error })
}
}
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>
</head>
<body/>

View File

@ -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
}
</script>
{#if showTemplateSelection}
@ -172,7 +183,7 @@
</ModalContent>
{:else}
<ModalContent
title={template?.fromFile ? "Import app" : "Create app"}
title={getModalTitle()}
confirmText={template?.fromFile ? "Import app" : "Create app"}
onConfirm={createNewApp}
onCancel={inline ? () => (template = null) : null}

View File

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

View File

@ -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")) {

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/client",
"version": "0.9.176-alpha.0",
"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.0",
"@budibase/bbui": "^0.9.180-alpha.0",
"@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",
"shortid": "^2.2.15",
"svelte-spa-router": "^3.0.5"

View File

@ -8,6 +8,12 @@
import { Modal, ModalContent, ActionButton } from "@budibase/bbui"
import { onDestroy } from "svelte"
const MessageTypes = {
NOTIFICATION: "notification",
CLOSE_SCREEN_MODAL: "close-screen-modal",
INVALIDATE_DATASOURCE: "invalidate-datasource",
}
let iframe
let listenersAttached = false
@ -21,32 +27,33 @@
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 = () => {
// Mirror datasource invalidation to keep the parent window up to date
iframe.contentWindow.addEventListener(
"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)
window.addEventListener("message", receiveMessage)
}
const handleCancel = () => {
peekStore.actions.hidePeek()
iframe.contentWindow.removeEventListener(
"invalidate-datasource",
invalidateDataSource
)
iframe.contentWindow.removeEventListener(
"close-screen-modal",
peekStore.actions.hidePeek
)
iframe.contentWindow.removeEventListener("notification", proxyNotification)
window.removeEventListener("message", receiveMessage)
}
const handleFullscreen = () => {

View File

@ -4,11 +4,7 @@ import { findComponentById, findComponentPathById } from "../utils/components"
import { pingEndUser } from "../api"
const dispatchEvent = (type, data = {}) => {
window.dispatchEvent(
new CustomEvent("bb-event", {
detail: { type, data },
})
)
window.parent.postMessage({ type, data })
}
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
// can also invalidate the same datasource
window.dispatchEvent(
new CustomEvent("invalidate-datasource", {
window.parent.postMessage({
type: "close-screen-modal",
detail: { dataSourceId },
})
)
let invalidations = [dataSourceId]

View File

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

View File

@ -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 => {

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
"version": "0.9.176-alpha.0",
"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.0",
"@budibase/client": "^0.9.176-alpha.0",
"@budibase/string-templates": "^0.9.176-alpha.0",
"@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",

View File

@ -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 }
}
}

View File

@ -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,7 +77,9 @@ exports.buildSchema = async ctx => {
}
exports.trigger = async ctx => {
const db = new CouchDB(ctx.params.instance)
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) {
@ -89,11 +92,19 @@ exports.trigger = async ctx => {
await triggers.externalTrigger(target, {
body: ctx.request.body,
...ctx.request.body,
appId: ctx.params.instance,
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.",
}
}
}
}

View File

@ -85,19 +85,6 @@ exports.run = async function ({ inputs }) {
const request = {
method: requestMethod,
}
if (
requestBody &&
requestBody.length !== 0 &&
BODY_REQUESTS.indexOf(requestMethod) !== -1
) {
request.body =
typeof requestBody === "string"
? requestBody
: JSON.stringify(requestBody)
request.headers = {
"Content-Type": "application/json",
}
if (headers) {
try {
const customHeaders =
@ -110,6 +97,19 @@ exports.run = async function ({ inputs }) {
}
}
}
if (
requestBody &&
requestBody.length !== 0 &&
BODY_REQUESTS.indexOf(requestMethod) !== -1
) {
request.body =
typeof requestBody === "string"
? requestBody
: JSON.stringify(requestBody)
request.headers = {
...request.headers,
"Content-Type": "application/json",
}
}
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 */

View File

@ -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

View File

@ -142,13 +142,11 @@ module RestModule {
}
async parseResponse(response: any) {
switch (this.headers.Accept) {
case "application/json":
const contentType = response.headers.get("content-type")
if (contentType && contentType.indexOf("application/json") !== -1) {
return await response.json()
case "text/html":
} else {
return await response.text()
default:
return await response.json()
}
}
@ -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),
})

View File

@ -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")

View File

@ -124,7 +124,10 @@ function copyExistingPropsOver(
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 errors: { [key: string]: string } = {}
for (let [name, table] of Object.entries(tables)) {

View File

@ -5,21 +5,18 @@ 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
// 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()
}

View File

@ -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)

View File

@ -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`)
}
}
}

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",
"version": "0.9.176-alpha.0",
"version": "0.9.180-alpha.0",
"description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs",
"module": "dist/bundle.mjs",

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/worker",
"email": "hi@budibase.com",
"version": "0.9.176-alpha.0",
"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.0",
"@budibase/string-templates": "^0.9.176-alpha.0",
"@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",

View File

@ -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.
*/

View File

@ -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,