1
0
Fork 0
mirror of synced 2024-07-03 05:20:32 +12:00

Merge pull request #2904 from Budibase/develop

Develop -> Master
This commit is contained in:
Martin McKeaveney 2021-10-06 16:26:05 +01:00 committed by GitHub
commit 526584d179
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 253 additions and 199 deletions

View file

@ -1,5 +1,5 @@
{ {
"version": "0.9.152", "version": "0.9.153-alpha.1",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/auth", "name": "@budibase/auth",
"version": "0.9.152", "version": "0.9.153-alpha.1",
"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

@ -20,6 +20,10 @@ const getErrorMessage = () => {
return done.mock.calls[0][2].message return done.mock.calls[0][2].message
} }
const saveUser = async (user) => {
return await db.put(user)
}
describe("third party common", () => { describe("third party common", () => {
describe("authenticateThirdParty", () => { describe("authenticateThirdParty", () => {
let thirdPartyUser let thirdPartyUser
@ -36,7 +40,7 @@ describe("third party common", () => {
describe("validation", () => { describe("validation", () => {
const testValidation = async (message) => { const testValidation = async (message) => {
await authenticateThirdParty(thirdPartyUser, false, done) await authenticateThirdParty(thirdPartyUser, false, done, saveUser)
expect(done.mock.calls.length).toBe(1) expect(done.mock.calls.length).toBe(1)
expect(getErrorMessage()).toContain(message) expect(getErrorMessage()).toContain(message)
} }
@ -78,7 +82,7 @@ describe("third party common", () => {
describe("when the user doesn't exist", () => { describe("when the user doesn't exist", () => {
describe("when a local account is required", () => { describe("when a local account is required", () => {
it("returns an error message", async () => { it("returns an error message", async () => {
await authenticateThirdParty(thirdPartyUser, true, done) await authenticateThirdParty(thirdPartyUser, true, done, saveUser)
expect(done.mock.calls.length).toBe(1) expect(done.mock.calls.length).toBe(1)
expect(getErrorMessage()).toContain("Email does not yet exist. You must set up your local budibase account first.") expect(getErrorMessage()).toContain("Email does not yet exist. You must set up your local budibase account first.")
}) })
@ -86,7 +90,7 @@ describe("third party common", () => {
describe("when a local account isn't required", () => { describe("when a local account isn't required", () => {
it("creates and authenticates the user", async () => { it("creates and authenticates the user", async () => {
await authenticateThirdParty(thirdPartyUser, false, done) await authenticateThirdParty(thirdPartyUser, false, done, saveUser)
const user = expectUserIsAuthenticated() const user = expectUserIsAuthenticated()
expectUserIsSynced(user, thirdPartyUser) expectUserIsSynced(user, thirdPartyUser)
expect(user.roles).toStrictEqual({}) expect(user.roles).toStrictEqual({})
@ -123,7 +127,7 @@ describe("third party common", () => {
}) })
it("syncs and authenticates the user", async () => { it("syncs and authenticates the user", async () => {
await authenticateThirdParty(thirdPartyUser, true, done) await authenticateThirdParty(thirdPartyUser, true, done, saveUser)
const user = expectUserIsAuthenticated() const user = expectUserIsAuthenticated()
expectUserIsSynced(user, thirdPartyUser) expectUserIsSynced(user, thirdPartyUser)
@ -139,7 +143,7 @@ describe("third party common", () => {
}) })
it("syncs and authenticates the user", async () => { it("syncs and authenticates the user", async () => {
await authenticateThirdParty(thirdPartyUser, true, done) await authenticateThirdParty(thirdPartyUser, true, done, saveUser)
const user = expectUserIsAuthenticated() const user = expectUserIsAuthenticated()
expectUserIsSynced(user, thirdPartyUser) expectUserIsSynced(user, thirdPartyUser)

View file

@ -1,6 +1,7 @@
const env = require("../../environment") const env = require("../../environment")
const jwt = require("jsonwebtoken") const jwt = require("jsonwebtoken")
const { generateGlobalUserID } = require("../../db/utils") const { generateGlobalUserID } = require("../../db/utils")
const { saveUser } = require("../../utils")
const { authError } = require("./utils") const { authError } = require("./utils")
const { newid } = require("../../hashing") const { newid } = require("../../hashing")
const { createASession } = require("../../security/sessions") const { createASession } = require("../../security/sessions")
@ -14,7 +15,8 @@ const fetch = require("node-fetch")
exports.authenticateThirdParty = async function ( exports.authenticateThirdParty = async function (
thirdPartyUser, thirdPartyUser,
requireLocalAccount = true, requireLocalAccount = true,
done done,
saveUserFn = saveUser
) { ) {
if (!thirdPartyUser.provider) { if (!thirdPartyUser.provider) {
return authError(done, "third party user provider required") return authError(done, "third party user provider required")
@ -71,7 +73,13 @@ exports.authenticateThirdParty = async function (
dbUser = await syncUser(dbUser, thirdPartyUser) dbUser = await syncUser(dbUser, thirdPartyUser)
// create or sync the user // create or sync the user
const response = await db.put(dbUser) let response
try {
response = await saveUserFn(dbUser, getTenantId(), false, false)
} catch (err) {
return authError(done, err)
}
dbUser._rev = response.rev dbUser._rev = response.rev
// authenticate // authenticate

View file

@ -265,7 +265,7 @@ exports.downloadTarball = async (url, bucketName, path) => {
const tmpPath = join(budibaseTempDir(), path) const tmpPath = join(budibaseTempDir(), path)
await streamPipeline(response.body, zlib.Unzip(), tar.extract(tmpPath)) await streamPipeline(response.body, zlib.Unzip(), tar.extract(tmpPath))
if (!env.isTest()) { if (!env.isTest() && env.SELF_HOSTED) {
await exports.uploadDirectory(bucketName, tmpPath, path) await exports.uploadDirectory(bucketName, tmpPath, path)
} }
// return the temporary path incase there is a use for it // return the temporary path incase there is a use for it

View file

@ -107,3 +107,13 @@ exports.lookupTenantId = async userId => {
} }
return tenantId return tenantId
} }
// lookup, could be email or userId, either will return a doc
exports.getTenantUser = async identifier => {
const db = getDB(PLATFORM_INFO_DB)
try {
return await db.get(identifier)
} catch (err) {
return null
}
}

View file

@ -1,10 +1,24 @@
const { DocumentTypes, SEPARATOR, ViewNames } = require("./db/utils") const {
DocumentTypes,
SEPARATOR,
ViewNames,
generateGlobalUserID,
} = require("./db/utils")
const jwt = require("jsonwebtoken") const jwt = require("jsonwebtoken")
const { options } = require("./middleware/passport/jwt") const { options } = require("./middleware/passport/jwt")
const { createUserEmailView } = require("./db/views") const { createUserEmailView } = require("./db/views")
const { Headers } = require("./constants") const { Headers, UserStatus } = require("./constants")
const { getGlobalDB } = require("./tenancy") const {
getGlobalDB,
updateTenantId,
getTenantUser,
tryAddTenant,
} = require("./tenancy")
const environment = require("./environment") const environment = require("./environment")
const accounts = require("./cloud/accounts")
const { hash } = require("./hashing")
const userCache = require("./cache/user")
const env = require("./environment")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR const APP_PREFIX = DocumentTypes.APP + SEPARATOR
@ -131,3 +145,93 @@ exports.getGlobalUserByEmail = async email => {
} }
} }
} }
exports.saveUser = async (
user,
tenantId,
hashPassword = true,
requirePassword = true
) => {
if (!tenantId) {
throw "No tenancy specified."
}
// need to set the context for this request, as specified
updateTenantId(tenantId)
// specify the tenancy incase we're making a new admin user (public)
const db = getGlobalDB(tenantId)
let { email, password, _id } = user
// make sure another user isn't using the same email
let dbUser
if (email) {
// check budibase users inside the tenant
dbUser = await exports.getGlobalUserByEmail(email)
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
throw `Email address ${email} already in use.`
}
// check budibase users in other tenants
if (env.MULTI_TENANCY) {
dbUser = await getTenantUser(email)
if (dbUser != null && dbUser.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
// check root account users in account portal
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
const account = await accounts.getAccount(email)
if (account && account.verified && account.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
} else {
dbUser = await db.get(_id)
}
// get the password, make sure one is defined
let hashedPassword
if (password) {
hashedPassword = hashPassword ? await hash(password) : password
} else if (dbUser) {
hashedPassword = dbUser.password
} else if (requirePassword) {
throw "Password must be specified."
}
_id = _id || generateGlobalUserID()
user = {
createdAt: Date.now(),
...dbUser,
...user,
_id,
password: hashedPassword,
tenantId,
}
// make sure the roles object is always present
if (!user.roles) {
user.roles = {}
}
// add the active status to a user if its not provided
if (user.status == null) {
user.status = UserStatus.ACTIVE
}
try {
const response = await db.put({
password: hashedPassword,
...user,
})
await tryAddTenant(tenantId, _id, email)
await userCache.invalidateUser(response.id)
return {
_id: response.id,
_rev: response.rev,
email,
}
} catch (err) {
if (err.status === 409) {
throw "User exists already"
} else {
throw err
}
}
}

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.152", "version": "0.9.153-alpha.1",
"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.152", "version": "0.9.153-alpha.1",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -65,10 +65,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.152", "@budibase/bbui": "^0.9.153-alpha.1",
"@budibase/client": "^0.9.152", "@budibase/client": "^0.9.153-alpha.1",
"@budibase/colorpicker": "1.1.2", "@budibase/colorpicker": "1.1.2",
"@budibase/string-templates": "^0.9.152", "@budibase/string-templates": "^0.9.153-alpha.1",
"@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

@ -17,7 +17,7 @@ export default class Automation {
this.automation.testData = data this.automation.testData = data
} }
addBlock(block) { addBlock(block, idx) {
// Make sure to add trigger if doesn't exist // Make sure to add trigger if doesn't exist
if (!this.hasTrigger() && block.type === "TRIGGER") { if (!this.hasTrigger() && block.type === "TRIGGER") {
const trigger = { id: generate(), ...block } const trigger = { id: generate(), ...block }
@ -26,10 +26,7 @@ export default class Automation {
} }
const newBlock = { id: generate(), ...block } const newBlock = { id: generate(), ...block }
this.automation.definition.steps = [ this.automation.definition.steps.splice(idx, 0, newBlock)
...this.automation.definition.steps,
newBlock,
]
return newBlock return newBlock
} }

View file

@ -104,9 +104,12 @@ const automationActions = store => ({
return state return state
}) })
}, },
addBlockToAutomation: block => { addBlockToAutomation: (block, blockIdx) => {
store.update(state => { store.update(state => {
const newBlock = state.selectedAutomation.addBlock(cloneDeep(block)) const newBlock = state.selectedAutomation.addBlock(
cloneDeep(block),
blockIdx
)
state.selectedBlock = newBlock state.selectedBlock = newBlock
return state return state
}) })

View file

@ -1,10 +1,9 @@
<script> <script>
import { ModalContent, Layout, Detail, Body, Icon } from "@budibase/bbui" import { ModalContent, Layout, Detail, Body, Icon } from "@budibase/bbui"
import { automationStore } from "builderStore" import { automationStore } from "builderStore"
import { database } from "stores/backend"
import { externalActions } from "./ExternalActions" import { externalActions } from "./ExternalActions"
$: instanceId = $database._id
export let blockIdx
let selectedAction let selectedAction
let actionVal let actionVal
let actions = Object.entries($automationStore.blockDefinitions.ACTION) let actions = Object.entries($automationStore.blockDefinitions.ACTION)
@ -39,7 +38,8 @@
) )
automationStore.actions.addBlockToAutomation(newBlock) automationStore.actions.addBlockToAutomation(newBlock)
await automationStore.actions.save( await automationStore.actions.save(
$automationStore.selectedAutomation?.automation $automationStore.selectedAutomation?.automation,
blockIdx
) )
} }
</script> </script>

View file

@ -4,9 +4,9 @@
import FlowItem from "./FlowItem.svelte" import FlowItem from "./FlowItem.svelte"
import TestDataModal from "./TestDataModal.svelte" import TestDataModal from "./TestDataModal.svelte"
import { flip } from "svelte/animate" import { flip } from "svelte/animate"
import { fade, fly } from "svelte/transition" import { fly } from "svelte/transition"
import { import {
Detail, Heading,
Icon, Icon,
ActionButton, ActionButton,
notifications, notifications,
@ -57,26 +57,24 @@
<div class="content"> <div class="content">
<div class="title"> <div class="title">
<div class="subtitle"> <div class="subtitle">
<Detail size="L">{automation.name}</Detail> <Heading size="S">{automation.name}</Heading>
<div <div style="display:flex;">
style="display:flex; <div class="iconPadding">
color: var(--spectrum-global-color-gray-400);"
>
<span class="iconPadding">
<div class="icon"> <div class="icon">
<Icon <Icon
on:click={confirmDeleteDialog.show} on:click={confirmDeleteDialog.show}
hoverable hoverable
size="M"
name="DeleteOutline" name="DeleteOutline"
/> />
</div> </div>
</span> </div>
<ActionButton <ActionButton
on:click={() => { on:click={() => {
testDataModal.show() testDataModal.show()
}} }}
icon="MultipleCheck" icon="MultipleCheck"
size="S">Run test</ActionButton size="M">Run test</ActionButton
> >
</div> </div>
</div> </div>
@ -84,16 +82,11 @@
{#each blocks as block, idx (block.id)} {#each blocks as block, idx (block.id)}
<div <div
class="block" class="block"
animate:flip={{ duration: 800 }} animate:flip={{ duration: 500 }}
in:fade|local in:fly|local={{ x: 500, duration: 1500 }}
out:fly|local={{ x: 500 }} out:fly|local={{ x: 500, duration: 800 }}
> >
<FlowItem {testDataModal} {testAutomation} {onSelect} {block} /> <FlowItem {testDataModal} {testAutomation} {onSelect} {block} />
{#if idx !== blocks.length - 1}
<div class="separator" />
<Icon name="AddCircle" size="S" />
<div class="separator" />
{/if}
</div> </div>
{/each} {/each}
</div> </div>
@ -114,14 +107,6 @@
</div> </div>
<style> <style>
.separator {
width: 1px;
height: 25px;
border-left: 1px dashed var(--grey-4);
color: var(--grey-4);
/* center horizontally */
align-self: center;
}
.canvas { .canvas {
margin: 0 -40px calc(-1 * var(--spacing-l)) -40px; margin: 0 -40px calc(-1 * var(--spacing-l)) -40px;
overflow-y: auto; overflow-y: auto;
@ -153,11 +138,14 @@
padding-bottom: var(--spacing-xl); padding-bottom: var(--spacing-xl);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
}
.iconPadding {
padding-top: var(--spacing-s);
} }
.icon { .icon {
cursor: pointer; cursor: pointer;
display: flex;
padding-right: var(--spacing-m); padding-right: var(--spacing-m);
} }
</style> </style>

View file

@ -14,7 +14,6 @@
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte" import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
import ResultsModal from "./ResultsModal.svelte" import ResultsModal from "./ResultsModal.svelte"
import ActionModal from "./ActionModal.svelte" import ActionModal from "./ActionModal.svelte"
import { database } from "stores/backend"
import { externalActions } from "./ExternalActions" import { externalActions } from "./ExternalActions"
export let onSelect export let onSelect
@ -29,7 +28,6 @@
$: testResult = $automationStore.selectedAutomation.testResults?.steps.filter( $: testResult = $automationStore.selectedAutomation.testResults?.steps.filter(
step => step.stepId === block.stepId step => step.stepId === block.stepId
) )
$: instanceId = $database._id
$: isTrigger = block.type === "TRIGGER" $: isTrigger = block.type === "TRIGGER"
@ -40,6 +38,10 @@
$: blockIdx = steps.findIndex(step => step.id === block.id) $: blockIdx = steps.findIndex(step => step.id === block.id)
$: lastStep = !isTrigger && blockIdx + 1 === steps.length $: lastStep = !isTrigger && blockIdx + 1 === steps.length
$: totalBlocks =
$automationStore.selectedAutomation?.automation?.definition?.steps.length +
1
// Logic for hiding / showing the add button.first we check if it has a child // Logic for hiding / showing the add button.first we check if it has a child
// then we check to see whether its inputs have been commpleted // then we check to see whether its inputs have been commpleted
$: disableAddButton = isTrigger $: disableAddButton = isTrigger
@ -167,13 +169,24 @@
</Modal> </Modal>
<Modal bind:this={actionModal} width="30%"> <Modal bind:this={actionModal} width="30%">
<ActionModal bind:blockComplete /> <ActionModal {blockIdx} bind:blockComplete />
</Modal> </Modal>
<Modal bind:this={webhookModal} width="30%"> <Modal bind:this={webhookModal} width="30%">
<CreateWebhookModal /> <CreateWebhookModal />
</Modal> </Modal>
</div> </div>
<div class="separator" />
<Icon
on:click={() => actionModal.show()}
disabled={!hasCompletedInputs}
hoverable
name="AddCircle"
size="S"
/>
{#if isTrigger ? totalBlocks > 1 : blockIdx !== totalBlocks - 2}
<div class="separator" />
{/if}
<style> <style>
.center-items { .center-items {
@ -191,8 +204,7 @@
.block { .block {
width: 360px; width: 360px;
font-size: 16px; font-size: 16px;
background-color: var(--spectrum-alias-background-color-secondary); background-color: var(--background);
color: var(--grey-9);
border: 1px solid var(--spectrum-global-color-gray-300); border: 1px solid var(--spectrum-global-color-gray-300);
border-radius: 4px 4px 4px 4px; border-radius: 4px 4px 4px 4px;
} }
@ -200,4 +212,13 @@
.blockSection { .blockSection {
padding: var(--spacing-xl); padding: var(--spacing-xl);
} }
.separator {
width: 1px;
height: 25px;
border-left: 1px dashed var(--grey-4);
color: var(--grey-4);
/* center horizontally */
align-self: center;
}
</style> </style>

View file

@ -5,24 +5,29 @@
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte" import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
import { datasources } from "stores/backend" import { datasources } from "stores/backend"
import { IntegrationNames } from "constants" import { IntegrationNames } from "constants"
import cloneDeep from "lodash/cloneDeepWith"
export let integration export let integration
export let modal
// kill the reference so the input isn't saved
let config = cloneDeep(integration)
function prepareData() { function prepareData() {
let datasource = {} let datasource = {}
let existingTypeCount = $datasources.list.filter( let existingTypeCount = $datasources.list.filter(
ds => ds.source == integration.type ds => ds.source == config.type
).length ).length
let baseName = IntegrationNames[integration.type] let baseName = IntegrationNames[config.type]
let name = let name =
existingTypeCount == 0 ? baseName : `${baseName}-${existingTypeCount + 1}` existingTypeCount == 0 ? baseName : `${baseName}-${existingTypeCount + 1}`
datasource.type = "datasource" datasource.type = "datasource"
datasource.source = integration.type datasource.source = config.type
datasource.config = integration.config datasource.config = config.config
datasource.name = name datasource.name = name
datasource.plus = integration.plus datasource.plus = config.plus
return datasource return datasource
} }
@ -48,9 +53,10 @@
</script> </script>
<ModalContent <ModalContent
title={`Connect to ${IntegrationNames[integration.type]}`} title={`Connect to ${IntegrationNames[config.type]}`}
onConfirm={() => saveDatasource()} onConfirm={() => saveDatasource()}
confirmText={integration.plus onCancel={() => modal.show()}
confirmText={config.plus
? "Fetch tables from database" ? "Fetch tables from database"
: "Save and continue to query"} : "Save and continue to query"}
cancelText="Back" cancelText="Back"
@ -62,10 +68,7 @@
</Body> </Body>
</Layout> </Layout>
<IntegrationConfigForm <IntegrationConfigForm schema={config.schema} integration={config.config} />
schema={integration.schema}
bind:integration={integration.config}
/>
</ModalContent> </ModalContent>
<style> <style>

View file

@ -27,13 +27,13 @@
urlTenantId = hostParts[0] urlTenantId = hostParts[0]
} }
// no tenant in the url - send to account portal to fix this
if (!urlTenantId) {
window.location.href = $admin.accountPortalUrl
return
}
if (user && user.tenantId) { if (user && user.tenantId) {
// no tenant in the url - send to account portal to fix this
if (!urlTenantId) {
window.location.href = $admin.accountPortalUrl
return
}
if (user.tenantId !== urlTenantId) { if (user.tenantId !== urlTenantId) {
// user should not be here - play it safe and log them out // user should not be here - play it safe and log them out
await auth.logout() await auth.logout()

View file

@ -8,6 +8,7 @@
Input, Input,
Layout, Layout,
notifications, notifications,
Link,
} from "@budibase/bbui" } from "@budibase/bbui"
import { goto, params } from "@roxi/routify" import { goto, params } from "@roxi/routify"
import { auth, organisation, oidc, admin } from "stores/portal" import { auth, organisation, oidc, admin } from "stores/portal"
@ -97,6 +98,16 @@
</ActionButton> </ActionButton>
{/if} {/if}
</Layout> </Layout>
{#if cloud}
<Body size="xs" textAlign="center">
By using Budibase Cloud
<br />
you are agreeing to our
<Link href="https://budibase.com/eula" target="_blank"
>License Agreement</Link
>
</Body>
{/if}
</Layout> </Layout>
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "0.9.152", "version": "0.9.153-alpha.1",
"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

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "0.9.152", "version": "0.9.153-alpha.1",
"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.152", "@budibase/bbui": "^0.9.153-alpha.1",
"@budibase/standard-components": "^0.9.139", "@budibase/standard-components": "^0.9.139",
"@budibase/string-templates": "^0.9.152", "@budibase/string-templates": "^0.9.153-alpha.1",
"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

@ -353,7 +353,7 @@
} }
/* Reduce padding */ /* Reduce padding */
.mobile .main { .mobile:not(.layout--none) .main {
padding: 16px; padding: 16px;
} }

View file

@ -17,7 +17,17 @@
if (!bounds || !side) { if (!bounds || !side) {
return null return null
} }
const { left, top, width, height } = bounds
// Get preview offset
const root = document.getElementById("clip-root")
const rootBounds = root.getBoundingClientRect()
// Subtract preview offset from bounds
let { left, top, width, height } = bounds
left -= rootBounds.left
top -= rootBounds.top
// Determine position
if (side === Sides.Top || side === Sides.Bottom) { if (side === Sides.Top || side === Sides.Bottom) {
return { return {
top: side === Sides.Top ? top - 4 : top + height, top: side === Sides.Top ? top - 4 : top + height,

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.152", "version": "0.9.153-alpha.1",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -66,9 +66,9 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.152", "@budibase/auth": "^0.9.153-alpha.1",
"@budibase/client": "^0.9.152", "@budibase/client": "^0.9.153-alpha.1",
"@budibase/string-templates": "^0.9.152", "@budibase/string-templates": "^0.9.153-alpha.1",
"@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

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "0.9.152", "version": "0.9.153-alpha.1",
"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.152", "version": "0.9.153-alpha.1",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -27,8 +27,8 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.152", "@budibase/auth": "^0.9.153-alpha.1",
"@budibase/string-templates": "^0.9.152", "@budibase/string-templates": "^0.9.153-alpha.1",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@techpass/passport-openidconnect": "^0.3.0", "@techpass/passport-openidconnect": "^0.3.0",
"aws-sdk": "^2.811.0", "aws-sdk": "^2.811.0",

View file

@ -1,29 +1,24 @@
const { const {
generateGlobalUserID,
getGlobalUserParams, getGlobalUserParams,
StaticDatabases, StaticDatabases,
generateNewUsageQuotaDoc, generateNewUsageQuotaDoc,
} = require("@budibase/auth/db") } = require("@budibase/auth/db")
const { hash, getGlobalUserByEmail } = require("@budibase/auth").utils const { hash, getGlobalUserByEmail, saveUser } = require("@budibase/auth").utils
const { UserStatus, EmailTemplatePurpose } = require("../../../constants") const { EmailTemplatePurpose } = require("../../../constants")
const { checkInviteCode } = require("../../../utilities/redis") const { checkInviteCode } = require("../../../utilities/redis")
const { sendEmail } = require("../../../utilities/email") const { sendEmail } = require("../../../utilities/email")
const { user: userCache } = require("@budibase/auth/cache") const { user: userCache } = require("@budibase/auth/cache")
const { invalidateSessions } = require("@budibase/auth/sessions") const { invalidateSessions } = require("@budibase/auth/sessions")
const CouchDB = require("../../../db")
const accounts = require("@budibase/auth/accounts") const accounts = require("@budibase/auth/accounts")
const { const {
getGlobalDB, getGlobalDB,
getTenantId, getTenantId,
getTenantUser,
doesTenantExist, doesTenantExist,
tryAddTenant,
updateTenantId,
} = require("@budibase/auth/tenancy") } = require("@budibase/auth/tenancy")
const { removeUserFromInfoDB } = require("@budibase/auth/deprovision") const { removeUserFromInfoDB } = require("@budibase/auth/deprovision")
const env = require("../../../environment") const env = require("../../../environment")
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
async function allUsers() { async function allUsers() {
const db = getGlobalDB() const db = getGlobalDB()
const response = await db.allDocs( const response = await db.allDocs(
@ -34,96 +29,6 @@ async function allUsers() {
return response.rows.map(row => row.doc) return response.rows.map(row => row.doc)
} }
async function saveUser(
user,
tenantId,
hashPassword = true,
requirePassword = true
) {
if (!tenantId) {
throw "No tenancy specified."
}
// need to set the context for this request, as specified
updateTenantId(tenantId)
// specify the tenancy incase we're making a new admin user (public)
const db = getGlobalDB(tenantId)
let { email, password, _id } = user
// make sure another user isn't using the same email
let dbUser
if (email) {
// check budibase users inside the tenant
dbUser = await getGlobalUserByEmail(email)
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
throw `Email address ${email} already in use.`
}
// check budibase users in other tenants
if (env.MULTI_TENANCY) {
dbUser = await getTenantUser(email)
if (dbUser != null && dbUser.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
// check root account users in account portal
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
const account = await accounts.getAccount(email)
if (account && account.verified && account.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
} else {
dbUser = await db.get(_id)
}
// get the password, make sure one is defined
let hashedPassword
if (password) {
hashedPassword = hashPassword ? await hash(password) : password
} else if (dbUser) {
hashedPassword = dbUser.password
} else if (requirePassword) {
throw "Password must be specified."
}
_id = _id || generateGlobalUserID()
user = {
createdAt: Date.now(),
...dbUser,
...user,
_id,
password: hashedPassword,
tenantId,
}
// make sure the roles object is always present
if (!user.roles) {
user.roles = {}
}
// add the active status to a user if its not provided
if (user.status == null) {
user.status = UserStatus.ACTIVE
}
try {
const response = await db.put({
password: hashedPassword,
...user,
})
await tryAddTenant(tenantId, _id, email)
await userCache.invalidateUser(response.id)
return {
_id: response.id,
_rev: response.rev,
email,
}
} catch (err) {
if (err.status === 409) {
throw "User exists already"
} else {
throw err
}
}
}
exports.save = async ctx => { exports.save = async ctx => {
try { try {
ctx.body = await saveUser(ctx.request.body, getTenantId()) ctx.body = await saveUser(ctx.request.body, getTenantId())
@ -310,16 +215,6 @@ exports.find = async ctx => {
ctx.body = user ctx.body = user
} }
// lookup, could be email or userId, either will return a doc
const getTenantUser = async identifier => {
const db = new CouchDB(PLATFORM_INFO_DB)
try {
return await db.get(identifier)
} catch (err) {
return null
}
}
exports.tenantUserLookup = async ctx => { exports.tenantUserLookup = async ctx => {
const id = ctx.params.id const id = ctx.params.id
const user = await getTenantUser(id) const user = await getTenantUser(id)