diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml new file mode 100644 index 0000000000..a4cbc4e70a --- /dev/null +++ b/.github/workflows/release-selfhost.yml @@ -0,0 +1,57 @@ +name: Budibase Release + +on: + workflow_dispatch: + +env: + POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} + INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} + POSTHOG_URL: ${{ secrets.POSTHOG_URL }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: yarn + - run: yarn bootstrap + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + + - name: 'Get Previous tag' + id: previoustag + uses: "WyriHaximus/github-action-get-previous-tag@v1" + + - name: Build/release Docker images (Self Host) + if: ${{ github.event.inputs.release_self_host == 'Y' }} + run: | + docker login -u $DOCKER_USER -p $DOCKER_PASSWORD + yarn build + yarn build:docker:production + env: + DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} + BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }} + + - uses: azure/setup-helm@v1 + id: install + + # So, we need to inject the values into this + - run: yarn release:helm + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.1.0 + with: + charts_dir: docs + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b38a70eb7..40154be992 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,12 +4,6 @@ on: push: branches: - master - workflow_dispatch: - inputs: - release_self_host: - description: 'Release to self hosters? (Y/N)' - required: true - default: 'N' env: POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} @@ -54,7 +48,6 @@ jobs: uses: "WyriHaximus/github-action-get-previous-tag@v1" - name: Build/release Docker images - if: ${{ github.event.inputs.release_self_host != 'Y' }} run: | docker login -u $DOCKER_USER -p $DOCKER_PASSWORD yarn build @@ -62,28 +55,4 @@ jobs: env: DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} - BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }} - - - name: Build/release Docker images (Self Host) - if: ${{ github.event.inputs.release_self_host == 'Y' }} - run: | - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - yarn build - yarn build:docker - env: - DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} - BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }} - - - uses: azure/setup-helm@v1 - id: install - - # So, we need to inject the values into this - - run: yarn release:helm - - - name: Run chart-releaser - uses: helm/chart-releaser-action@v1.1.0 - with: - charts_dir: docs - env: - CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }} \ No newline at end of file diff --git a/lerna.json b/lerna.json index 9ac2a8d4d2..a687b57552 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.153-alpha.1", + "version": "0.9.160-alpha.0", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index 88d5ace67b..6469114278 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.153-alpha.1", + "version": "0.9.160-alpha.0", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/auth/src/db/constants.js b/packages/auth/src/db/constants.js index ad4f6c9f66..477968975a 100644 --- a/packages/auth/src/db/constants.js +++ b/packages/auth/src/db/constants.js @@ -1,5 +1,20 @@ exports.SEPARATOR = "_" +const PRE_APP = "app" +const PRE_DEV = "dev" + +exports.DocumentTypes = { + USER: "us", + WORKSPACE: "workspace", + CONFIG: "config", + TEMPLATE: "template", + APP: PRE_APP, + DEV: PRE_DEV, + APP_DEV: `${PRE_APP}${exports.SEPARATOR}${PRE_DEV}`, + APP_METADATA: `${PRE_APP}${exports.SEPARATOR}metadata`, + ROLE: "role", +} + exports.StaticDatabases = { GLOBAL: { name: "global-db", diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 09e2ff6314..fa162603e6 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -2,8 +2,8 @@ const { newid } = require("../hashing") const Replication = require("./Replication") const { DEFAULT_TENANT_ID } = require("../constants") const env = require("../environment") -const { StaticDatabases, SEPARATOR } = require("./constants") -const { getTenantId } = require("../tenancy") +const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants") +const { getTenantId, getTenantIDFromAppID } = require("../tenancy") const fetch = require("node-fetch") const { getCouch } = require("./index") @@ -15,25 +15,11 @@ exports.ViewNames = { exports.StaticDatabases = StaticDatabases -const PRE_APP = "app" -const PRE_DEV = "dev" - -const DocumentTypes = { - USER: "us", - WORKSPACE: "workspace", - CONFIG: "config", - TEMPLATE: "template", - APP: PRE_APP, - DEV: PRE_DEV, - APP_DEV: `${PRE_APP}${SEPARATOR}${PRE_DEV}`, - APP_METADATA: `${PRE_APP}${SEPARATOR}metadata`, - ROLE: "role", -} - exports.DocumentTypes = DocumentTypes exports.APP_PREFIX = DocumentTypes.APP + SEPARATOR exports.APP_DEV = exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR exports.SEPARATOR = SEPARATOR +exports.getTenantIDFromAppID = getTenantIDFromAppID /** * If creating DB allDocs/query params with only a single top level ID this can be used, this @@ -70,26 +56,6 @@ function isDevApp(app) { return exports.isDevAppID(app.appId) } -/** - * Given an app ID this will attempt to retrieve the tenant ID from it. - * @return {null|string} The tenant ID found within the app ID. - */ -exports.getTenantIDFromAppID = appId => { - if (!appId) { - return null - } - const split = appId.split(SEPARATOR) - const hasDev = split[1] === DocumentTypes.DEV - if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) { - return null - } - if (hasDev) { - return split[2] - } else { - return split[1] - } -} - /** * Generates a new workspace ID. * @returns {string} The new workspace ID which the workspace doc can be stored under. diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index 48a24ad0bc..0ee17265ce 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -191,6 +191,12 @@ class RedisWrapper { } } + async getTTL(key) { + const db = this._db + const prefixedKey = addDbPrefix(db, key) + return CLIENT.ttl(prefixedKey) + } + async setExpiry(key, expirySeconds) { const db = this._db const prefixedKey = addDbPrefix(db, key) diff --git a/packages/auth/src/tenancy/tenancy.js b/packages/auth/src/tenancy/tenancy.js index 67dbfd5619..2cd05ea925 100644 --- a/packages/auth/src/tenancy/tenancy.js +++ b/packages/auth/src/tenancy/tenancy.js @@ -1,5 +1,5 @@ const { getDB } = require("../db") -const { SEPARATOR, StaticDatabases } = require("../db/constants") +const { SEPARATOR, StaticDatabases, DocumentTypes } = require("../db/constants") const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("./context") const env = require("../environment") @@ -117,3 +117,34 @@ exports.getTenantUser = async identifier => { return null } } + +/** + * Given an app ID this will attempt to retrieve the tenant ID from it. + * @return {null|string} The tenant ID found within the app ID. + */ +exports.getTenantIDFromAppID = appId => { + if (!appId) { + return null + } + const split = appId.split(SEPARATOR) + const hasDev = split[1] === DocumentTypes.DEV + if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) { + return null + } + if (hasDev) { + return split[2] + } else { + return split[1] + } +} + +exports.isUserInAppTenant = (appId, user = null) => { + let userTenantId + if (user) { + userTenantId = user.tenantId || DEFAULT_TENANT_ID + } else { + userTenantId = getTenantId() + } + const tenantId = exports.getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID + return tenantId === userTenantId +} diff --git a/packages/bbui/package.json b/packages/bbui/package.json index facc46b82d..6c838587b0 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.153-alpha.1", + "version": "0.9.160-alpha.0", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/cypress/integration/addMultiOptionDatatype.spec.js b/packages/builder/cypress/integration/addMultiOptionDatatype.spec.js new file mode 100644 index 0000000000..bc2619c53d --- /dev/null +++ b/packages/builder/cypress/integration/addMultiOptionDatatype.spec.js @@ -0,0 +1,41 @@ +context("Add Multi-Option Datatype", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + + it("should create a new table, with data", () => { + cy.createTable("Multi Data") + cy.addColumn("Multi Data", "Test Data", "Multi-select", "1\n2\n3\n4\n5") + cy.addRowMultiValue(["1", "2", "3", "4", "5"]) + }) + + it ("should add form with multi select picker, containing 5 options", () => { + cy.navigateToFrontend() + cy.wait(500) + // Add data provider + cy.get(`[data-cy="category-Data Provider"]`).click() + cy.get('[data-cy="dataSource-prop-control"]').click() + cy.get(".dropdown").contains("Multi Data").click() + cy.wait(500) + // Add Form with schema to match table + cy.addComponent("Form", "Form") + cy.get('[data-cy="dataSource-prop-control"').click() + cy.get(".dropdown").contains("Multi Data").click() + cy.wait(500) + // Add multi-select picker to form + cy.addComponent("Form", "Multi-select Picker").then((componentId) => { + cy.get('[data-cy="field-prop-control"]').type("Test Data").type('{enter}') + cy.wait(1000) + cy.getComponent(componentId).contains("Choose some options").click() + // Check picker has 5 items + cy.getComponent(componentId).find('li').should('have.length', 5) + // Select all items + for (let i = 1; i < 6; i++) { + cy.getComponent(componentId).find('li').contains(i).click() + } + // Check items have been selected + cy.getComponent(componentId).find('.spectrum-Picker-label').contains("(5)") + }) + }) +}) diff --git a/packages/builder/cypress/integration/addRadioButtons.spec.js b/packages/builder/cypress/integration/addRadioButtons.spec.js new file mode 100644 index 0000000000..68d0a74d55 --- /dev/null +++ b/packages/builder/cypress/integration/addRadioButtons.spec.js @@ -0,0 +1,35 @@ +context("Add Radio Buttons", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + +it("should add Radio Buttons options picker on form, add data, and confirm", () => { + cy.navigateToFrontend() + cy.addComponent("Form", "Form") + cy.addComponent("Form", "Options Picker").then((componentId) => { + // Provide field setting + cy.get(`[data-cy="field-prop-control"]`).type("1") + // Open dropdown and select Radio buttons + cy.get(`[data-cy="optionsType-prop-control"]`).click().then(() => { + cy.get('.spectrum-Popover').contains('Radio buttons') + .wait(500) + .click() + }) + const radioButtonsTotal = 3 + // Add values and confirm total + addRadioButtonData(radioButtonsTotal) + cy.getComponent(componentId).find('[type="radio"]') + .should('have.length', radioButtonsTotal) + }) + }) + + const addRadioButtonData = (totalRadioButtons) => { + cy.get(`[data-cy="optionsSource-prop-control"]`).click().then(() => { + cy.get('.spectrum-Popover').contains('Custom') + .wait(500) + .click() + }) + cy.addCustomSourceOptions(totalRadioButtons) + } +}) diff --git a/packages/builder/cypress/integration/createTable.spec.js b/packages/builder/cypress/integration/createTable.spec.js index 876894e5aa..ccb9ae7929 100644 --- a/packages/builder/cypress/integration/createTable.spec.js +++ b/packages/builder/cypress/integration/createTable.spec.js @@ -37,7 +37,7 @@ context("Create a Table", () => { cy.get(".spectrum-Modal input").clear() cy.get(".spectrum-Modal input").type("RoverUpdated") cy.contains("Save").click() - cy.contains("RoverUpdated").should("have.text", "RoverUpdated") + cy.contains("Updated").should("have.text", "Updated") }) it("deletes a row", () => { diff --git a/packages/builder/cypress/integration/createView.spec.js b/packages/builder/cypress/integration/createView.spec.js index 62d0740a39..e82ab67c0d 100644 --- a/packages/builder/cypress/integration/createView.spec.js +++ b/packages/builder/cypress/integration/createView.spec.js @@ -62,7 +62,7 @@ context("Create a View", () => { cy.get(".spectrum-Picker-label").eq(1).click() cy.contains("age").click({ force: true }) - cy.contains("Save").click() + cy.get(".spectrum-Button").contains("Save").click({ force: true }) }) cy.wait(1000) @@ -123,7 +123,7 @@ context("Create a View", () => { cy.contains(".nav-item", "Test View") .find(".actions .icon") .click({ force: true }) - cy.contains("Edit").click() + cy.get(".spectrum-Menu-itemLabel").contains("Edit").click() cy.get(".modal-inner-wrapper").within(() => { cy.get("input").type(" Updated") cy.contains("Save").click() @@ -138,8 +138,8 @@ context("Create a View", () => { .click({ force: true }) cy.contains("Delete").click() cy.contains("Delete View").click() - cy.wait(1000) - cy.contains("TestView Updated").should("not.be.visible") + cy.wait(500) + cy.contains("TestView Updated").should("not.exist") }) }) diff --git a/packages/builder/cypress/integration/customThemingProperties.spec.js b/packages/builder/cypress/integration/customThemingProperties.spec.js new file mode 100644 index 0000000000..47cb81a8a6 --- /dev/null +++ b/packages/builder/cypress/integration/customThemingProperties.spec.js @@ -0,0 +1,61 @@ +context("Custom Theming Properties", () => { + before(() => { + cy.login() + cy.createTestApp() + cy.navigateToFrontend() + }) + + // Default Values + // Button roundness = Large + // Primary colour = Blue 600 + // Primary colour (hover) = Blue 500 + // Navigation bar background colour = Gray 100 + // Navigation bar text colour = Gray 800 + it("should reset the color property values", () => { + // Open Theme modal and change colours + cy.get(".spectrum-ActionButton-label").contains("Theme").click() + cy.get(".spectrum-Picker").contains("Large").click() + .parents() + .get(".spectrum-Menu-itemLabel").contains("None").click() + changeThemeColors() + // Reset colours + cy.get(".spectrum-Button-label").contains("Reset").click({force: true}) + // Check values have reset + checkThemeColorDefaults() + }) + + const changeThemeColors = () => { + // Changes the theme colours + cy.get(".spectrum-FieldLabel").contains("Primary color") + .parent().find(".container.svelte-z3cm5a").click() + .find('[title="Red 400"]').click() + cy.get(".spectrum-FieldLabel").contains("Primary color (hover)") + .parent().find(".container.svelte-z3cm5a").click() + .find('[title="Orange 400"]').click() + cy.get(".spectrum-FieldLabel").contains("Navigation bar background color") + .parent().find(".container.svelte-z3cm5a").click() + .find('[title="Yellow 400"]').click() + cy.get(".spectrum-FieldLabel").contains("Navigation bar text color") + .parent().find(".container.svelte-z3cm5a").click() + .find('[title="Green 400"]').click() + } + + const checkThemeColorDefaults = () => { + cy.get(".spectrum-FieldLabel").contains("Primary color") + .parent().find(".container.svelte-z3cm5a").click() + .get('[title="Blue 600"]').children().find('[aria-label="Checkmark"]') + cy.get(".spectrum-Dialog-grid").click() + cy.get(".spectrum-FieldLabel").contains("Primary color (hover)") + .parent().find(".container.svelte-z3cm5a").click() + .get('[title="Blue 500"]').children().find('[aria-label="Checkmark"]') + cy.get(".spectrum-Dialog-grid").click() + cy.get(".spectrum-FieldLabel").contains("Navigation bar background color") + .parent().find(".container.svelte-z3cm5a").click() + .get('[title="Gray 100"]').children().find('[aria-label="Checkmark"]') + cy.get(".spectrum-Dialog-grid").click() + cy.get(".spectrum-FieldLabel").contains("Navigation bar text color") + .parent().find(".container.svelte-z3cm5a").click() + .get('[title="Gray 800"]').children().find('[aria-label="Checkmark"]') + } + +}) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index cbdaa27270..c8e01435aa 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -5,9 +5,13 @@ // *********************************************** // +Cypress.on('uncaught:exception', (err, runnable) => { + return false +}) + Cypress.Commands.add("login", () => { cy.visit(`localhost:${Cypress.env("PORT")}/builder`) - cy.wait(500) + cy.wait(2000) cy.url().then(url => { if (url.includes("builder/admin")) { // create admin user @@ -22,6 +26,7 @@ Cypress.Commands.add("login", () => { cy.get("input").first().type("test@test.com") cy.get('input[type="password"]').type("test") cy.get("button").first().click() + cy.wait(1000) }) } }) @@ -100,24 +105,32 @@ Cypress.Commands.add("createTable", tableName => { cy.contains(tableName).should("be.visible") }) -Cypress.Commands.add("addColumn", (tableName, columnName, type) => { - // Select Table - cy.selectTable(tableName) - cy.contains(".nav-item", tableName).click() - cy.contains("Create column").click() +Cypress.Commands.add( + "addColumn", + (tableName, columnName, type, multiOptions = null) => { + // Select Table + cy.selectTable(tableName) + cy.contains(".nav-item", tableName).click() + cy.contains("Create column").click() - // Configure column - cy.get(".spectrum-Modal").within(() => { - cy.get("input").first().type(columnName).blur() + // Configure column + cy.get(".spectrum-Modal").within(() => { + cy.get("input").first().type(columnName).blur() - // Unset table display column - cy.contains("display column").click({ force: true }) - cy.get(".spectrum-Picker-label").click() - cy.contains(type).click() + // Unset table display column + cy.contains("display column").click({ force: true }) + cy.get(".spectrum-Picker-label").click() + cy.contains(type).click() - cy.contains("Save Column").click() - }) -}) + // Add options for Multi-select Type + if (multiOptions !== null) { + cy.get(".spectrum-Textfield-input").eq(1).type(multiOptions) + } + + cy.contains("Save Column").click() + }) + } +) Cypress.Commands.add("addRow", values => { cy.contains("Create row").click() @@ -129,6 +142,21 @@ Cypress.Commands.add("addRow", values => { }) }) +Cypress.Commands.add("addRowMultiValue", values => { + cy.contains("Create row").click() + cy.get(".spectrum-Form-itemField") + .click() + .then(() => { + cy.get(".spectrum-Popover").within(() => { + for (let i = 0; i < values.length; i++) { + cy.get(".spectrum-Menu-item").eq(i).click() + } + }) + cy.get(".spectrum-Dialog-grid").click("top") + cy.get(".spectrum-ButtonGroup").contains("Create").click() + }) +}) + Cypress.Commands.add("createUser", email => { // quick hacky recorded way to create a user cy.contains("Users").click() @@ -147,7 +175,9 @@ Cypress.Commands.add("addComponent", (category, component) => { if (category) { cy.get(`[data-cy="category-${category}"]`).click() } - cy.get(`[data-cy="component-${component}"]`).click() + if (component) { + cy.get(`[data-cy="component-${component}"]`).click() + } cy.wait(1000) cy.location().then(loc => { const params = loc.pathname.split("/") @@ -169,8 +199,11 @@ Cypress.Commands.add("getComponent", componentId => { }) Cypress.Commands.add("navigateToFrontend", () => { + // Clicks on Design tab and then the Home nav item cy.wait(1000) cy.contains("Design").click() + cy.get(".spectrum-Search").type("/") + cy.get(".nav-item").contains("Home").click() }) Cypress.Commands.add("createScreen", (screenName, route) => { @@ -193,3 +226,24 @@ Cypress.Commands.add("selectTable", tableName => { cy.expandBudibaseConnection() cy.contains(".nav-item", tableName).click() }) + +Cypress.Commands.add("addCustomSourceOptions", totalOptions => { + cy.get(".spectrum-ActionButton") + .contains("Define Options") + .click() + .then(() => { + for (let i = 0; i < totalOptions; i++) { + // Add radio button options + cy.get(".spectrum-Button") + .contains("Add Option") + .click({ force: true }) + .then(() => { + cy.wait(500) + cy.get("[placeholder='Label']").eq(i).type(i) + cy.get("[placeholder='Value']").eq(i).type(i) + }) + } + // Save options + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + }) +}) diff --git a/packages/builder/package.json b/packages/builder/package.json index 9ee2044a1a..71e7e8c9a8 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.153-alpha.1", + "version": "0.9.160-alpha.0", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.153-alpha.1", - "@budibase/client": "^0.9.153-alpha.1", + "@budibase/bbui": "^0.9.160-alpha.0", + "@budibase/client": "^0.9.160-alpha.0", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.153-alpha.1", + "@budibase/string-templates": "^0.9.160-alpha.0", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index c567454eff..9110aa1430 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -67,6 +67,14 @@ export const getFrontendStore = () => { initialise: async pkg => { const { layouts, screens, application, clientLibPath } = pkg const components = await fetchComponentLibDefinitions(application.appId) + // make sure app isn't locked + if ( + components && + components.status === 400 && + components.message?.includes("lock") + ) { + throw { ok: false, reason: "locked" } + } store.update(state => ({ ...state, libraries: application.componentLibraries, diff --git a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte index 9913333054..a52fbdb177 100644 --- a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte @@ -68,5 +68,5 @@ {/if} - + diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte index 3c6fa83c01..796f70b43b 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte @@ -11,16 +11,18 @@ import ICONS from "./icons" let openDataSources = [] - $: enrichedDataSources = $datasources.list.map(datasource => { - const selected = $datasources.selected === datasource._id - const open = openDataSources.includes(datasource._id) - const containsSelected = containsActiveEntity(datasource) - return { - ...datasource, - selected, - open: selected || open || containsSelected, - } - }) + $: enrichedDataSources = Array.isArray($datasources.list) + ? $datasources.list.map(datasource => { + const selected = $datasources.selected === datasource._id + const open = openDataSources.includes(datasource._id) + const containsSelected = containsActiveEntity(datasource) + return { + ...datasource, + selected, + open: selected || open || containsSelected, + } + }) + : [] $: openDataSource = enrichedDataSources.find(x => x.open) $: { // Ensure the open data source is always included in the list of open diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte index 23dc63a060..93deb3eb39 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte @@ -3,7 +3,7 @@ import { ModalContent, notifications, Body, Layout } from "@budibase/bbui" import analytics, { Events } from "analytics" import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte" - import { datasources } from "stores/backend" + import { datasources, tables } from "stores/backend" import { IntegrationNames } from "constants" import cloneDeep from "lodash/cloneDeepWith" @@ -21,7 +21,9 @@ let baseName = IntegrationNames[config.type] let name = - existingTypeCount == 0 ? baseName : `${baseName}-${existingTypeCount + 1}` + existingTypeCount === 0 + ? baseName + : `${baseName}-${existingTypeCount + 1}` datasource.type = "datasource" datasource.source = config.type @@ -37,6 +39,8 @@ // Create datasource const resp = await datasources.save(datasource, datasource.plus) + // update the tables incase data source plus + await tables.fetch() await datasources.select(resp._id) $goto(`./datasource/${resp._id}`) notifications.success(`Datasource updated successfully.`) diff --git a/packages/builder/src/components/design/AppPreview/iframeTemplate.js b/packages/builder/src/components/design/AppPreview/iframeTemplate.js index d992be8cd7..58557273f2 100644 --- a/packages/builder/src/components/design/AppPreview/iframeTemplate.js +++ b/packages/builder/src/components/design/AppPreview/iframeTemplate.js @@ -64,7 +64,7 @@ export default ` appId, theme, customTheme, - previewDevice + previewDevice, } = parsed // Set some flags so the app knows we're in the builder diff --git a/packages/builder/src/components/start/TemplateList.svelte b/packages/builder/src/components/start/TemplateList.svelte index 576415762a..deddd92880 100644 --- a/packages/builder/src/components/start/TemplateList.svelte +++ b/packages/builder/src/components/start/TemplateList.svelte @@ -33,7 +33,10 @@ {/each}
onSelect(null)}> -
+
Start from scratch @@ -74,7 +77,7 @@ align-items: center; cursor: pointer; border-radius: 4px; - background: #1a1a1a; + background: var(--background-alt); padding: 8px 16px; } diff --git a/packages/builder/src/constants/lucene.js b/packages/builder/src/constants/lucene.js index 00da0c29bc..132790739c 100644 --- a/packages/builder/src/constants/lucene.js +++ b/packages/builder/src/constants/lucene.js @@ -44,6 +44,15 @@ export const OperatorOptions = { }, } +export const NoEmptyFilterStrings = [ + OperatorOptions.StartsWith.value, + OperatorOptions.Like.value, + OperatorOptions.Equals.value, + OperatorOptions.NotEquals.value, + OperatorOptions.Contains.value, + OperatorOptions.NotContains.value, +] + /** * Returns the valid operator options for a certain data type * @param type the data type diff --git a/packages/builder/src/helpers/lucene.js b/packages/builder/src/helpers/lucene.js index 03baa751cc..d344b462d1 100644 --- a/packages/builder/src/helpers/lucene.js +++ b/packages/builder/src/helpers/lucene.js @@ -1,3 +1,26 @@ +import { NoEmptyFilterStrings } from "../constants/lucene" + +/** + * Removes any fields that contain empty strings that would cause inconsistent + * behaviour with how backend tables are filtered (no value means no filter). + */ +function cleanupQuery(query) { + if (!query) { + return query + } + for (let filterField of NoEmptyFilterStrings) { + if (!query[filterField]) { + continue + } + for (let [key, value] of Object.entries(query[filterField])) { + if (!value || value === "") { + delete query[filterField][key] + } + } + } + return query +} + /** * Builds a lucene JSON query from the filter structure generated in the builder * @param filter the builder filter structure @@ -76,6 +99,8 @@ export const luceneQuery = (docs, query) => { if (!query) { return docs } + // make query consistent first + query = cleanupQuery(query) // Iterates over a set of filters and evaluates a fail function against a doc const match = (type, failFn) => doc => { diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index aaf948883d..603fb62d99 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -8,7 +8,7 @@ import NPSFeedbackForm from "components/feedback/NPSFeedbackForm.svelte" import { get } from "builderStore/api" import { auth, admin } from "stores/portal" - import { isActive, goto, layout } from "@roxi/routify" + import { isActive, goto, layout, redirect } from "@roxi/routify" import Logo from "assets/bb-emblem.svg" import { capitalise } from "helpers" import UpgradeModal from "../../../../components/upgrade/UpgradeModal.svelte" @@ -34,7 +34,16 @@ const pkg = await res.json() if (res.ok) { - await store.actions.initialise(pkg) + try { + await store.actions.initialise(pkg) + // edge case, lock wasn't known to client when it re-directed, or user went directly + } catch (err) { + if (!err.ok && err.reason === "locked") { + $redirect("../../") + } else { + throw err + } + } await automationStore.actions.fetch() await roles.fetch() return pkg diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte index 02f49bec5a..8fca18d29d 100644 --- a/packages/builder/src/pages/builder/portal/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/_layout.svelte @@ -46,11 +46,16 @@ title: "Theming", href: "/builder/portal/settings/theming", }, - { - title: "Updates", - href: "/builder/portal/settings/update", - }, ]) + + if (!$adminStore.cloud) { + menu = menu.concat([ + { + title: "Updates", + href: "/builder/portal/settings/update", + }, + ]) + } } else { menu = menu.concat([ { diff --git a/packages/builder/src/pages/builder/portal/settings/update.svelte b/packages/builder/src/pages/builder/portal/settings/update.svelte index 3931203f44..5deb724a7c 100644 --- a/packages/builder/src/pages/builder/portal/settings/update.svelte +++ b/packages/builder/src/pages/builder/portal/settings/update.svelte @@ -10,14 +10,14 @@ Label, } from "@budibase/bbui" import api from "builderStore/api" - import { auth } from "stores/portal" + import { auth, admin } from "stores/portal" import { redirect } from "@roxi/routify" let version // Only admins allowed here $: { - if (!$auth.isAdmin) { + if (!$auth.isAdmin || $admin.cloud) { $redirect("../../portal") } } diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index f522095473..7beb25a1b4 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -56,7 +56,7 @@ export function createAuthStore() { analytics.identify(user._id, user) analytics.showChat({ email: user.email, - created_at: user.createdAt || Date.now(), + created_at: (user.createdAt || Date.now()) / 1000, name: user.name, user_id: user._id, tenant: user.tenantId, diff --git a/packages/cli/package.json b/packages/cli/package.json index 37d316b17a..7e677bf08e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.153-alpha.1", + "version": "0.9.160-alpha.0", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 3e932a9f1c..a933a2bef2 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.153-alpha.1", + "version": "0.9.160-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.153-alpha.1", + "@budibase/bbui": "^0.9.160-alpha.0", "@budibase/standard-components": "^0.9.139", - "@budibase/string-templates": "^0.9.153-alpha.1", + "@budibase/string-templates": "^0.9.160-alpha.0", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/client/src/components/MadeInBudibase.svelte b/packages/client/src/components/MadeInBudibase.svelte new file mode 100644 index 0000000000..2e5d6336f1 --- /dev/null +++ b/packages/client/src/components/MadeInBudibase.svelte @@ -0,0 +1,42 @@ + +
+ Budibase +

Made In Budibase

+
+
+ + diff --git a/packages/server/package.json b/packages/server/package.json index 6a04c3f4e9..5325a68557 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.153-alpha.1", + "version": "0.9.160-alpha.0", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -66,9 +66,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.153-alpha.1", - "@budibase/client": "^0.9.153-alpha.1", - "@budibase/string-templates": "^0.9.153-alpha.1", + "@budibase/auth": "^0.9.160-alpha.0", + "@budibase/client": "^0.9.160-alpha.0", + "@budibase/string-templates": "^0.9.160-alpha.0", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", diff --git a/packages/server/src/api/controllers/analytics.js b/packages/server/src/api/controllers/analytics.js index e6e5d6a748..0c0761840e 100644 --- a/packages/server/src/api/controllers/analytics.js +++ b/packages/server/src/api/controllers/analytics.js @@ -21,9 +21,12 @@ exports.endUserPing = async ctx => { return } - posthogClient.capture("budibase:end_user_ping", { - userId: ctx.user && ctx.user._id, - appId: ctx.appId, + posthogClient.capture({ + event: "budibase:end_user_ping", + distinctId: ctx.user && ctx.user._id, + properties: { + appId: ctx.appId, + }, }) ctx.body = { diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index 5078218fc7..ac88599713 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -2,6 +2,7 @@ const CouchDB = require("../../db") const { outputProcessing } = require("../../utilities/rowProcessor") const { InternalTables } = require("../../db/utils") const { getFullUser } = require("../../utilities/users") +const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") exports.fetchSelf = async ctx => { const appId = ctx.appId @@ -27,7 +28,14 @@ exports.fetchSelf = async ctx => { ...metadata, }) } catch (err) { - ctx.body = user + // user didn't exist in app, don't pretend they do + if (user.roleId === BUILTIN_ROLE_IDS.PUBLIC) { + ctx.body = {} + } + // user has a role of some sort, return them + else { + ctx.body = user + } } } else { ctx.body = user diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index 3e9c707e69..222f921018 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -178,7 +178,12 @@ module External { manyRelationships: ManyRelationship[] = [] for (let [key, field] of Object.entries(table.schema)) { // if set already, or not set just skip it - if (!row[key] || newRow[key] || field.autocolumn) { + if ((!row[key] && row[key] !== "") || newRow[key] || field.autocolumn) { + continue + } + // if its an empty string then it means return the column to null (if possible) + if (row[key] === "") { + newRow[key] = null continue } // parse floats/numbers diff --git a/packages/server/src/api/controllers/row/external.js b/packages/server/src/api/controllers/row/external.js index 3a96064a9f..534f2207b0 100644 --- a/packages/server/src/api/controllers/row/external.js +++ b/packages/server/src/api/controllers/row/external.js @@ -2,6 +2,7 @@ const { DataSourceOperation, SortDirection, FieldTypes, + NoEmptyFilterStrings, } = require("../../../constants") const { breakExternalTableId, @@ -11,6 +12,19 @@ const ExternalRequest = require("./ExternalRequest") const CouchDB = require("../../../db") async function handleRequest(appId, operation, tableId, opts = {}) { + // make sure the filters are cleaned up, no empty strings for equals, fuzzy or string + if (opts && opts.filters) { + for (let filterField of NoEmptyFilterStrings) { + if (!opts.filters[filterField]) { + continue + } + for (let [key, value] of Object.entries(opts.filters[filterField])) { + if (!value || value === "") { + delete opts.filters[filterField][key] + } + } + } + } return new ExternalRequest(appId, operation, tableId, opts.datasource).run( opts ) diff --git a/packages/server/src/api/controllers/view/utils.js b/packages/server/src/api/controllers/view/utils.js index 1f3b980882..27fccaf47f 100644 --- a/packages/server/src/api/controllers/view/utils.js +++ b/packages/server/src/api/controllers/view/utils.js @@ -2,6 +2,8 @@ const { ViewNames, generateMemoryViewID, getMemoryViewParams, + DocumentTypes, + SEPARATOR, } = require("../../../db/utils") const env = require("../../../environment") @@ -10,6 +12,11 @@ exports.getView = async (db, viewName) => { const designDoc = await db.get("_design/database") return designDoc.views[viewName] } else { + // This is a table view, don't read the view from the DB + if (viewName.startsWith(DocumentTypes.TABLE + SEPARATOR)) { + return null + } + const viewDoc = await db.get(generateMemoryViewID(viewName)) return viewDoc.view } diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index 492ebfff5b..48117fbe4b 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -9,7 +9,6 @@ jest.mock("../../../utilities/workerRequests", () => ({ getGlobalSelf: jest.fn(() => { return {} }), - addAppRoleToUser: jest.fn(), deleteGlobalUser: jest.fn(), })) diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index bea58fd260..d19f9ff313 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -6,6 +6,29 @@ exports.JobQueues = { AUTOMATIONS: "automationQueue", } +const FilterTypes = { + STRING: "string", + FUZZY: "fuzzy", + RANGE: "range", + EQUAL: "equal", + NOT_EQUAL: "notEqual", + EMPTY: "empty", + NOT_EMPTY: "notEmpty", + CONTAINS: "contains", + NOT_CONTAINS: "notContains", + ONE_OF: "oneOf", +} + +exports.FilterTypes = FilterTypes +exports.NoEmptyFilterStrings = [ + FilterTypes.STRING, + FilterTypes.FUZZY, + FilterTypes.EQUAL, + FilterTypes.NOT_EQUAL, + FilterTypes.CONTAINS, + FilterTypes.NOT_CONTAINS, +] + exports.FieldTypes = { STRING: "string", LONGFORM: "longform", diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index 6bd2137f99..4130900217 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -55,6 +55,12 @@ function addFilters( query = query[fnc](key, "ilike", `${value}%`) }) } + if (filters.fuzzy) { + iterate(filters.fuzzy, (key, value) => { + const fnc = allOr ? "orWhere" : "where" + query = query[fnc](key, "ilike", `%${value}%`) + }) + } if (filters.range) { iterate(filters.range, (key, value) => { if (!value.high || !value.low) { @@ -135,6 +141,12 @@ function buildCreate( const { endpoint, body } = json let query: KnexQuery = knex(endpoint.entityId) const parsedBody = parseBody(body) + // make sure no null values in body for creation + for (let [key, value] of Object.entries(parsedBody)) { + if (value == null) { + delete parsedBody[key] + } + } // mysql can't use returning if (opts.disableReturning) { return query.insert(parsedBody) diff --git a/packages/server/src/middleware/builder.js b/packages/server/src/middleware/builder.js index 240a2d1912..8ea49a3b48 100644 --- a/packages/server/src/middleware/builder.js +++ b/packages/server/src/middleware/builder.js @@ -33,7 +33,7 @@ async function checkDevAppLocks(ctx) { return } if (!(await doesUserHaveLock(appId, ctx.user))) { - ctx.throw(403, "User does not hold app lock.") + ctx.throw(400, "User does not hold app lock.") } // they do have lock, update it diff --git a/packages/server/src/middleware/currentapp.js b/packages/server/src/middleware/currentapp.js index f43345b2fe..062c35a6ca 100644 --- a/packages/server/src/middleware/currentapp.js +++ b/packages/server/src/middleware/currentapp.js @@ -5,8 +5,10 @@ const { getRole } = require("@budibase/auth/roles") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") const { generateUserMetadataID } = require("../db/utils") const { dbExists } = require("@budibase/auth/db") +const { isUserInAppTenant } = require("@budibase/auth/tenancy") const { getCachedSelf } = require("../utilities/global") const CouchDB = require("../db") +const env = require("../environment") module.exports = async (ctx, next) => { // try to get the appID from the request @@ -45,11 +47,29 @@ module.exports = async (ctx, next) => { // retrieving global user gets the right role roleId = globalUser.roleId || BUILTIN_ROLE_IDS.BASIC } + // nothing more to do if (!appId) { return next() } + let noCookieSet = false + // if the user not in the right tenant then make sure they have no permissions + // need to judge this only based on the request app ID, + if ( + env.MULTI_TENANCY && + ctx.user && + requestAppId && + !isUserInAppTenant(requestAppId) + ) { + // don't error, simply remove the users rights (they are a public user) + delete ctx.user.builder + delete ctx.user.admin + delete ctx.user.roles + roleId = BUILTIN_ROLE_IDS.PUBLIC + noCookieSet = true + } + ctx.appId = appId if (roleId) { ctx.roleId = roleId @@ -64,9 +84,10 @@ module.exports = async (ctx, next) => { } } if ( - requestAppId !== appId || - appCookie == null || - appCookie.appId !== requestAppId + (requestAppId !== appId || + appCookie == null || + appCookie.appId !== requestAppId) && + !noCookieSet ) { setCookie(ctx, { appId }, Cookies.CurrentApp) } diff --git a/packages/server/src/utilities/global.js b/packages/server/src/utilities/global.js index 8f032df7f0..3637c11eea 100644 --- a/packages/server/src/utilities/global.js +++ b/packages/server/src/utilities/global.js @@ -6,13 +6,20 @@ const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") const { getDeployedAppID } = require("@budibase/auth/db") const { getGlobalUserParams } = require("@budibase/auth/db") const { user: userCache } = require("@budibase/auth/cache") -const { getGlobalDB } = require("@budibase/auth/tenancy") +const { getGlobalDB, isUserInAppTenant } = require("@budibase/auth/tenancy") +const env = require("../environment") exports.updateAppRole = (appId, user) => { - if (!user.roles) { + if (!user || !user.roles) { + return user + } + // if in an multi-tenancy environment make sure roles are never updated + if (env.MULTI_TENANCY && !isUserInAppTenant(appId, user)) { + delete user.builder + delete user.admin + user.roleId = BUILTIN_ROLE_IDS.PUBLIC return user } - // always use the deployed app user.roleId = user.roles[getDeployedAppID(appId)] // if a role wasn't found then either set as admin (builder) or public (everyone else) diff --git a/packages/server/src/utilities/workerRequests.js b/packages/server/src/utilities/workerRequests.js index c07c93ba70..6a15e1b243 100644 --- a/packages/server/src/utilities/workerRequests.js +++ b/packages/server/src/utilities/workerRequests.js @@ -2,7 +2,7 @@ const fetch = require("node-fetch") const env = require("../environment") const { checkSlashesInUrl } = require("./index") const { getDeployedAppID } = require("@budibase/auth/db") -const { updateAppRole, getGlobalUser } = require("./global") +const { updateAppRole } = require("./global") const { Headers } = require("@budibase/auth/constants") const { getTenantId, isTenantIdSet } = require("@budibase/auth/tenancy") @@ -98,38 +98,6 @@ exports.getGlobalSelf = async (ctx, appId = null) => { return json } -exports.addAppRoleToUser = async (ctx, appId, roleId, userId = null) => { - let user, - endpoint, - body = {} - if (!userId) { - user = await exports.getGlobalSelf(ctx) - endpoint = `/api/global/users/self` - } else { - user = await getGlobalUser(appId, userId) - body._id = userId - endpoint = `/api/global/users` - } - body = { - ...body, - roles: { - ...user.roles, - [getDeployedAppID(appId)]: roleId, - }, - } - const response = await fetch( - checkSlashesInUrl(env.WORKER_URL + endpoint), - request(ctx, { - method: "POST", - body, - }) - ) - if (response.status !== 200) { - ctx.throw(400, "Unable to save self globally.") - } - return response.json() -} - exports.removeAppFromUserRoles = async (ctx, appId) => { const deployedAppId = getDeployedAppID(appId) const response = await fetch( diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 401f34013e..bf397ae041 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.153-alpha.1", + "version": "0.9.160-alpha.0", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", @@ -20,7 +20,7 @@ "manifest": "node ./scripts/gen-collection-info.js" }, "dependencies": { - "@budibase/handlebars-helpers": "^0.11.6", + "@budibase/handlebars-helpers": "^0.11.7", "dayjs": "^1.10.4", "handlebars": "^4.7.6", "handlebars-utils": "^1.0.6", diff --git a/packages/string-templates/yarn.lock b/packages/string-templates/yarn.lock index 86592f1cec..b5abb1403a 100644 --- a/packages/string-templates/yarn.lock +++ b/packages/string-templates/yarn.lock @@ -270,15 +270,14 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/handlebars-helpers@^0.11.6": - version "0.11.6" - resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.6.tgz#00e924a0142aac41c07e3d104607759635eec952" - integrity sha512-FLtCWkh0cNHC0/X6Pt5Xjmp4/r4tCpv5f5sP1JcZsaSKPyE5gpNu/+fqUxgDTzVS3PVo0KE6hdRPKVWvVqwPEw== +"@budibase/handlebars-helpers@^0.11.7": + version "0.11.7" + resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.7.tgz#8e5f9843d7dd10503e9f608555a96ccf4d836c46" + integrity sha512-PvGHAv22cWSFExs1kc0WglwsmCEUEOqWvSp6JCFZwtc3qAAr5yMfLK8WGVQ63ALvyzWZiyxF+yrlzeeaohCMJw== dependencies: array-sort "^1.0.0" define-property "^2.0.2" extend-shallow "^3.0.2" - "falsey" "^1.0.0" for-in "^1.0.2" get-object "^0.2.0" get-value "^3.0.1" @@ -1734,11 +1733,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -"falsey@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/falsey/-/falsey-1.0.0.tgz#71bdd775c24edad9f2f5c015ce8be24400bb5d7d" - integrity sha512-zMDNZ/Ipd8MY0+346CPvhzP1AsiVyNfTOayJza4reAIWf72xbkuFUDcJNxSAsQE1b9Bu0wijKb8Ngnh/a7fI5w== - fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" diff --git a/packages/worker/package.json b/packages/worker/package.json index f0d196b2e6..ced8f73c07 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.153-alpha.1", + "version": "0.9.160-alpha.0", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -27,8 +27,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.153-alpha.1", - "@budibase/string-templates": "^0.9.153-alpha.1", + "@budibase/auth": "^0.9.160-alpha.0", + "@budibase/string-templates": "^0.9.160-alpha.0", "@koa/router": "^8.0.0", "@techpass/passport-openidconnect": "^0.3.0", "aws-sdk": "^2.811.0", diff --git a/packages/worker/src/api/controllers/global/auth.js b/packages/worker/src/api/controllers/global/auth.js index f3188d7777..9d7df7ca37 100644 --- a/packages/worker/src/api/controllers/global/auth.js +++ b/packages/worker/src/api/controllers/global/auth.js @@ -41,13 +41,24 @@ async function authInternal(ctx, user, err = null, info = null) { return ctx.throw(403, info ? info : "Unauthorized") } - // just store the user ID - ctx.cookies.set(Cookies.Auth, user.token, { + const config = { expires, path: "/", httpOnly: false, overwrite: true, - }) + } + + if (env.COOKIE_DOMAIN) { + config.domain = env.COOKIE_DOMAIN + } + + // just store the user ID + ctx.cookies.set(Cookies.Auth, user.token, config) + // get rid of any app cookies on login + // have to check test because this breaks cypress + if (!env.isTest()) { + clearCookie(ctx, Cookies.CurrentApp) + } } exports.authenticate = async (ctx, next) => { @@ -111,6 +122,7 @@ exports.resetUpdate = async ctx => { exports.logout = async ctx => { clearCookie(ctx, Cookies.Auth) + clearCookie(ctx, Cookies.CurrentApp) ctx.body = { message: "User logged out." } } diff --git a/packages/worker/src/api/routes/tests/realEmail.spec.js b/packages/worker/src/api/routes/tests/realEmail.spec.js index 845e31d911..54238e9270 100644 --- a/packages/worker/src/api/routes/tests/realEmail.spec.js +++ b/packages/worker/src/api/routes/tests/realEmail.spec.js @@ -17,25 +17,35 @@ describe("/api/global/email", () => { afterAll(setup.afterAll) async function sendRealEmail(purpose) { - await config.saveEtherealSmtpConfig() - await config.saveSettingsConfig() - const user = await config.getUser("test@test.com") - const res = await request - .post(`/api/global/email/send`) - .send({ - email: "test@test.com", - purpose, - userId: user._id, - }) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body.message).toBeDefined() - const testUrl = nodemailer.getTestMessageUrl(res.body) - console.log(`${purpose} URL: ${testUrl}`) - expect(testUrl).toBeDefined() - const response = await fetch(testUrl) - const text = await response.text() + let response, text + try { + await config.saveEtherealSmtpConfig() + await config.saveSettingsConfig() + const user = await config.getUser("test@test.com") + const res = await request + .post(`/api/global/email/send`) + .send({ + email: "test@test.com", + purpose, + userId: user._id, + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.message).toBeDefined() + const testUrl = nodemailer.getTestMessageUrl(res.body) + console.log(`${purpose} URL: ${testUrl}`) + expect(testUrl).toBeDefined() + response = await fetch(testUrl) + text = await response.text() + } catch (err) { + // ethereal hiccup, can't test right now + if (parseInt(err.status) >= 400) { + return + } else { + throw err + } + } let toCheckFor switch (purpose) { case EmailTemplatePurpose.WELCOME: diff --git a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js index 7f84de6b7d..9638e2a2a7 100644 --- a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js @@ -38,6 +38,9 @@ class TestConfiguration { request.request = { body: config, } + request.throw = (status, err) => { + throw { status, message: err } + } if (params) { request.params = params } diff --git a/packages/worker/src/environment.js b/packages/worker/src/environment.js index 0ef83a327e..74bd40314c 100644 --- a/packages/worker/src/environment.js +++ b/packages/worker/src/environment.js @@ -41,6 +41,7 @@ module.exports = { SMTP_PORT: process.env.SMTP_PORT, SMTP_FROM_ADDRESS: process.env.SMTP_FROM_ADDRESS, PLATFORM_URL: process.env.PLATFORM_URL, + COOKIE_DOMAIN: process.env.COOKIE_DOMAIN, _set(key, value) { process.env[key] = value module.exports[key] = value