Browse Source

Merge branch 'develop' of github.com:Budibase/budibase into fix/mike-fixes

gh-pages
mike12345567 5 years ago
parent
commit
3cc733eefd
  1. 2
      lerna.json
  2. 2
      packages/auth/package.json
  3. 14
      packages/auth/src/middleware/passport/tests/third-party-common.spec.js
  4. 12
      packages/auth/src/middleware/passport/third-party-common.js
  5. 2
      packages/auth/src/objectStore/index.js
  6. 10
      packages/auth/src/tenancy/tenancy.js
  7. 110
      packages/auth/src/utils.js
  8. 2
      packages/bbui/package.json
  9. 8
      packages/builder/package.json
  10. 7
      packages/builder/src/builderStore/store/automation/Automation.js
  11. 7
      packages/builder/src/builderStore/store/automation/index.js
  12. 6
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte
  13. 42
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
  14. 31
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte
  15. 25
      packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte
  16. 12
      packages/builder/src/pages/builder/_layout.svelte
  17. 11
      packages/builder/src/pages/builder/auth/login.svelte
  18. 2
      packages/cli/package.json
  19. 6
      packages/client/package.json
  20. 8
      packages/server/package.json
  21. 2
      packages/string-templates/package.json
  22. 6
      packages/worker/package.json
  23. 111
      packages/worker/src/api/controllers/global/users.js

2
lerna.json

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

2
packages/auth/package.json

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

14
packages/auth/src/middleware/passport/tests/third-party-common.spec.js

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

12
packages/auth/src/middleware/passport/third-party-common.js

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

2
packages/auth/src/objectStore/index.js

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

10
packages/auth/src/tenancy/tenancy.js

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

110
packages/auth/src/utils.js

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

2
packages/bbui/package.json

@ -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-alpha.0", "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",

8
packages/builder/package.json

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "0.9.152-alpha.0", "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-alpha.0", "@budibase/bbui": "^0.9.153-alpha.1",
"@budibase/client": "^0.9.152-alpha.0", "@budibase/client": "^0.9.153-alpha.1",
"@budibase/colorpicker": "1.1.2", "@budibase/colorpicker": "1.1.2",
"@budibase/string-templates": "^0.9.152-alpha.0", "@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",

7
packages/builder/src/builderStore/store/automation/Automation.js

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

7
packages/builder/src/builderStore/store/automation/index.js

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

6
packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte

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

42
packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte

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

31
packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte

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

25
packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte

@ -5,26 +5,31 @@
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte" import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
import { datasources, tables } from "stores/backend" import { datasources, tables } 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 existingTypeCount === 0
? baseName ? baseName
: `${baseName}-${existingTypeCount + 1}` : `${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
} }
@ -52,9 +57,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"
@ -66,10 +72,7 @@
</Body> </Body>
</Layout> </Layout>
<IntegrationConfigForm <IntegrationConfigForm schema={config.schema} integration={config.config} />
schema={integration.schema}
bind:integration={integration.config}
/>
</ModalContent> </ModalContent>
<style> <style>

12
packages/builder/src/pages/builder/_layout.svelte

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

11
packages/builder/src/pages/builder/auth/login.svelte

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

2
packages/cli/package.json

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

6
packages/client/package.json

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

8
packages/server/package.json

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

2
packages/string-templates/package.json

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

6
packages/worker/package.json

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

111
packages/worker/src/api/controllers/global/users.js

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

Loading…
Cancel
Save