{JSON.stringify(bindings, null, 2)}diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte index 3f390e0a4f..1d54c86b4a 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte @@ -1,6 +1,6 @@ diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte index 61777c0b7e..2f6ec51233 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte @@ -3,7 +3,7 @@ import { goto } from "@roxi/routify" import { views as viewsStore } from "stores/backend" import { tables } from "stores/backend" - import analytics from "analytics" + import analytics, { Events } from "analytics" let name let field @@ -21,7 +21,7 @@ field, }) notifications.success(`View ${name} created`) - analytics.captureEvent("View Created", { name }) + analytics.captureEvent(Events.VIEW.CREATED, { name }) $goto(`../../view/${name}`) } diff --git a/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte b/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte index 170bb75142..9c6f4956b0 100644 --- a/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte @@ -11,7 +11,7 @@ Icon, } from "@budibase/bbui" import { tables, views } from "stores/backend" - import analytics from "analytics" + import analytics, { Events } from "analytics" const CONDITIONS = [ { @@ -65,7 +65,7 @@ function saveView() { views.save(view) notifications.success(`View ${view.name} saved.`) - analytics.captureEvent("Added View Filter", { + analytics.captureEvent(Events.VIEW.ADDED_FILTER, { filters: JSON.stringify(view.filters), }) } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte index 84c737eb67..19713595ce 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte @@ -63,20 +63,19 @@ {#if openDataSources.includes(datasource._id)}
Produce a humanized duration left/until given an amount of time and the type of time measurement.
\n" } } -} \ No newline at end of file +} diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index c9ca671546..a244fab9ba 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.125-alpha.13", + "version": "0.9.140-alpha.6", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/string-templates/src/helpers/index.js b/packages/string-templates/src/helpers/index.js index 1ff729d187..23a20c90c7 100644 --- a/packages/string-templates/src/helpers/index.js +++ b/packages/string-templates/src/helpers/index.js @@ -19,6 +19,13 @@ const HELPERS = [ }), // this help is applied to all statements new Helper(HelperFunctionNames.ALL, value => { + if ( + value != null && + typeof value === "object" && + value.toString() === "[object Object]" + ) { + return new SafeString(JSON.stringify(value)) + } // null/undefined values produce bad results if (value == null || typeof value !== "string") { return value || "" diff --git a/packages/string-templates/test/basic.spec.js b/packages/string-templates/test/basic.spec.js index 4536a159df..2e63ce8a5f 100644 --- a/packages/string-templates/test/basic.spec.js +++ b/packages/string-templates/test/basic.spec.js @@ -81,6 +81,16 @@ describe("Test that the object processing works correctly", () => { expect(error).not.toBeNull() }) + it("check objects get converted to string JSON automatically", async () => { + const row = {a: 1} + const output = await processString("{{ trigger.row }}", { + trigger: { + row, + } + }) + expect(JSON.parse(output)).toEqual(row) + }) + it("should be able to handle null objects", async () => { let error = null try { diff --git a/packages/string-templates/yarn.lock b/packages/string-templates/yarn.lock index 0188a9ec1d..82f99d7b31 100644 --- a/packages/string-templates/yarn.lock +++ b/packages/string-templates/yarn.lock @@ -4633,9 +4633,9 @@ time-stamp@^1.0.1: integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= tmpl@1.0.x: - version "1.0.4" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" - integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: version "2.0.0" diff --git a/packages/worker/package.json b/packages/worker/package.json index 29300ba946..01fd3f0f8e 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.125-alpha.13", + "version": "0.9.140-alpha.6", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -25,8 +25,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.125-alpha.13", - "@budibase/string-templates": "^0.9.125-alpha.13", + "@budibase/auth": "^0.9.140-alpha.6", + "@budibase/string-templates": "^0.9.140-alpha.6", "@koa/router": "^8.0.0", "@techpass/passport-openidconnect": "^0.3.0", "aws-sdk": "^2.811.0", diff --git a/packages/worker/scripts/dev/manage.js b/packages/worker/scripts/dev/manage.js index f9a931110e..3df0beb23c 100644 --- a/packages/worker/scripts/dev/manage.js +++ b/packages/worker/scripts/dev/manage.js @@ -21,7 +21,7 @@ async function init() { COUCH_DB_PASSWORD: "budibase", // empty string is false MULTI_TENANCY: "", - ACCOUNT_PORTAL_URL: "http://localhost:3001", + ACCOUNT_PORTAL_URL: "http://localhost:10001", } let envFile = "" Object.keys(envFileJson).forEach(key => { diff --git a/packages/worker/scripts/jestSetup.js b/packages/worker/scripts/jestSetup.js index 374edfb946..89a517279a 100644 --- a/packages/worker/scripts/jestSetup.js +++ b/packages/worker/scripts/jestSetup.js @@ -1,5 +1,6 @@ const env = require("../src/environment") +env._set("SELF_HOSTED", "1") env._set("NODE_ENV", "jest") env._set("JWT_SECRET", "test-jwtsecret") env._set("LOG_LEVEL", "silent") diff --git a/packages/worker/src/api/controllers/global/auth.js b/packages/worker/src/api/controllers/global/auth.js index 2ae0bbafea..f3188d7777 100644 --- a/packages/worker/src/api/controllers/global/auth.js +++ b/packages/worker/src/api/controllers/global/auth.js @@ -96,7 +96,7 @@ exports.reset = async ctx => { exports.resetUpdate = async ctx => { const { resetCode, password } = ctx.request.body try { - const userId = await checkResetPasswordCode(resetCode) + const { userId } = await checkResetPasswordCode(resetCode) const db = getGlobalDB() const user = await db.get(userId) user.password = await hash(password) diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index 415808bf86..1375240f34 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -6,13 +6,11 @@ const { } = require("@budibase/auth/db") const { hash, getGlobalUserByEmail } = require("@budibase/auth").utils const { UserStatus, EmailTemplatePurpose } = require("../../../constants") -const { DEFAULT_TENANT_ID } = require("@budibase/auth/constants") const { checkInviteCode } = require("../../../utilities/redis") const { sendEmail } = require("../../../utilities/email") const { user: userCache } = require("@budibase/auth/cache") const { invalidateSessions } = require("@budibase/auth/sessions") const CouchDB = require("../../../db") -const env = require("../../../environment") const { getGlobalDB, getTenantId, @@ -33,7 +31,12 @@ async function allUsers() { return response.rows.map(row => row.doc) } -async function saveUser(user, tenantId, hashPassword = true) { +async function saveUser( + user, + tenantId, + hashPassword = true, + requirePassword = true +) { if (!tenantId) { throw "No tenancy specified." } @@ -59,12 +62,13 @@ async function saveUser(user, tenantId, hashPassword = true) { hashedPassword = hashPassword ? await hash(password) : password } else if (dbUser) { hashedPassword = dbUser.password - } else { + } else if (requirePassword) { throw "Password must be specified." } _id = _id || generateGlobalUserID() user = { + createdAt: Date.now(), ...dbUser, ...user, _id, @@ -108,16 +112,21 @@ exports.save = async ctx => { } } +const parseBooleanParam = param => { + if (param && param == "false") { + return false + } else { + return true + } +} + exports.adminUser = async ctx => { const { email, password, tenantId } = ctx.request.body // account portal sends a pre-hashed password - honour param to prevent double hashing - let hashPassword = ctx.request.query.hashPassword - if (hashPassword && hashPassword == "false") { - hashPassword = false - } else { - hashPassword = true - } + const hashPassword = parseBooleanParam(ctx.request.query.hashPassword) + // account portal sends no password for SSO users + const requirePassword = parseBooleanParam(ctx.request.query.requirePassword) if (await doesTenantExist(tenantId)) { ctx.throw(403, "Organisation already exists.") @@ -140,6 +149,7 @@ exports.adminUser = async ctx => { const user = { email: email, password: password, + createdAt: Date.now(), roles: {}, builder: { global: true, @@ -150,7 +160,7 @@ exports.adminUser = async ctx => { tenantId, } try { - ctx.body = await saveUser(user, tenantId, hashPassword) + ctx.body = await saveUser(user, tenantId, hashPassword, requirePassword) } catch (err) { ctx.throw(err.status || 400, err) } @@ -251,25 +261,14 @@ exports.find = async ctx => { ctx.body = user } -exports.tenantLookup = async ctx => { +exports.tenantUserLookup = async ctx => { const id = ctx.params.id // lookup, could be email or userId, either will return a doc const db = new CouchDB(PLATFORM_INFO_DB) - let tenantId = null try { - const doc = await db.get(id) - if (doc && doc.tenantId) { - tenantId = doc.tenantId - } + ctx.body = await db.get(id) } catch (err) { - if (!env.MULTI_TENANCY) { - tenantId = DEFAULT_TENANT_ID - } else { - ctx.throw(400, "No tenant found.") - } - } - ctx.body = { - tenantId, + ctx.throw(400, "No tenant user found.") } } diff --git a/packages/worker/src/api/controllers/global/workspaces.js b/packages/worker/src/api/controllers/global/workspaces.js index 95a1ec296d..48a710c92d 100644 --- a/packages/worker/src/api/controllers/global/workspaces.js +++ b/packages/worker/src/api/controllers/global/workspaces.js @@ -11,7 +11,7 @@ exports.save = async function (ctx) { } try { - const response = await db.post(workspaceDoc) + const response = await db.put(workspaceDoc) ctx.body = { _id: response.id, _rev: response.rev, diff --git a/packages/worker/src/api/controllers/system/environment.js b/packages/worker/src/api/controllers/system/environment.js index 305ccd7937..664e950797 100644 --- a/packages/worker/src/api/controllers/system/environment.js +++ b/packages/worker/src/api/controllers/system/environment.js @@ -3,7 +3,7 @@ const env = require("../../../environment") exports.fetch = async ctx => { ctx.body = { multiTenancy: !!env.MULTI_TENANCY, - cloud: !(env.SELF_HOSTED === "1"), + cloud: !env.SELF_HOSTED, accountPortalUrl: env.ACCOUNT_PORTAL_URL, } } diff --git a/packages/worker/src/api/routes/global/users.js b/packages/worker/src/api/routes/global/users.js index a0738acbf5..1a04944a30 100644 --- a/packages/worker/src/api/routes/global/users.js +++ b/packages/worker/src/api/routes/global/users.js @@ -10,7 +10,7 @@ function buildAdminInitValidation() { return joiValidator.body( Joi.object({ email: Joi.string().required(), - password: Joi.string().required(), + password: Joi.string(), tenantId: Joi.string().required(), }) .required() @@ -94,7 +94,7 @@ router controller.adminUser ) .get("/api/global/users/self", controller.getSelf) - .get("/api/global/users/tenant/:id", controller.tenantLookup) + .get("/api/global/users/tenant/:id", controller.tenantUserLookup) // global endpoint but needs to come at end (blocks other endpoints otherwise) .get("/api/global/users/:id", adminOnly, controller.find) diff --git a/packages/worker/src/environment.js b/packages/worker/src/environment.js index 12113c087c..646536f292 100644 --- a/packages/worker/src/environment.js +++ b/packages/worker/src/environment.js @@ -18,7 +18,7 @@ if (!LOADED && isDev() && !isTest()) { module.exports = { NODE_ENV: process.env.NODE_ENV, - SELF_HOSTED: process.env.SELF_HOSTED, + SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED), PORT: process.env.PORT, CLUSTER_PORT: process.env.CLUSTER_PORT, MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, diff --git a/packages/worker/src/utilities/email.js b/packages/worker/src/utilities/email.js index c32ff05cf5..d22933ef36 100644 --- a/packages/worker/src/utilities/email.js +++ b/packages/worker/src/utilities/email.js @@ -51,7 +51,7 @@ function createSMTPTransport(config) { async function getLinkCode(purpose, email, user, info = null) { switch (purpose) { case EmailTemplatePurpose.PASSWORD_RECOVERY: - return getResetPasswordCode(user._id) + return getResetPasswordCode(user._id, info) case EmailTemplatePurpose.INVITATION: return getInviteCode(email, info) default: diff --git a/packages/worker/src/utilities/redis.js b/packages/worker/src/utilities/redis.js index 6dd4491bc4..b644d1bc57 100644 --- a/packages/worker/src/utilities/redis.js +++ b/packages/worker/src/utilities/redis.js @@ -63,8 +63,8 @@ exports.shutdown = async () => { * @param {string} userId the ID of the user which is to be reset. * @return {Promise