From c6b1ea9a0fd69ff1d4d24708332fe2d670be3595 Mon Sep 17 00:00:00 2001 From: Christo Date: Thu, 8 Jul 2021 14:38:49 +0200 Subject: [PATCH 001/173] Feat: Add collection fields as "extra" query config for MongoDB --- .../integration/ExtraQueryConfig.svelte | 48 +++++++ .../components/integration/QueryViewer.svelte | 17 +++ packages/server/src/api/routes/query.js | 2 + packages/server/src/definitions/datasource.ts | 10 ++ packages/server/src/integrations/mongodb.ts | 125 ++++++++++++++++-- 5 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 packages/builder/src/components/integration/ExtraQueryConfig.svelte diff --git a/packages/builder/src/components/integration/ExtraQueryConfig.svelte b/packages/builder/src/components/integration/ExtraQueryConfig.svelte new file mode 100644 index 000000000..303beb1e4 --- /dev/null +++ b/packages/builder/src/components/integration/ExtraQueryConfig.svelte @@ -0,0 +1,48 @@ + + +{#each extraFields as { key, displayName, type }} +
+ + {#if type === "string"} + populateExtraQuery(extraQueryFields)} + bind:value={extraQueryFields[key]} + /> + {/if} + + {#if type === "list"} + queryConfig[verb]?.displayName || capitalise(verb)} />
+ {#if integrationInfo?.extra && query.queryVerb} + + {/if} {/if} diff --git a/packages/server/src/api/routes/query.js b/packages/server/src/api/routes/query.js index dd307c944..2bbadcb40 100644 --- a/packages/server/src/api/routes/query.js +++ b/packages/server/src/api/routes/query.js @@ -30,6 +30,7 @@ function generateQueryValidation() { default: Joi.string().allow(""), })), queryVerb: Joi.string().allow().required(), + extra: Joi.object().optional(), schema: Joi.object({}).required().unknown(true) })) } @@ -39,6 +40,7 @@ function generateQueryPreviewValidation() { return joiValidator.body(Joi.object({ fields: Joi.object().required(), queryVerb: Joi.string().allow().required(), + extra: Joi.object().optional(), datasourceId: Joi.string().required(), parameters: Joi.object({}).required().unknown(true) })) diff --git a/packages/server/src/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts index 22f199860..24d814494 100644 --- a/packages/server/src/definitions/datasource.ts +++ b/packages/server/src/definitions/datasource.ts @@ -49,6 +49,15 @@ export interface QueryDefinition { urlDisplay?: boolean } +export interface ExtraQueryConfig { + [key: string]: { + displayName: string, + type: string, + required: boolean + data?: object + } +} + export interface Integration { docs: string plus?: boolean @@ -58,6 +67,7 @@ export interface Integration { query: { [key: string]: QueryDefinition } + extra?: ExtraQueryConfig } export interface SearchFilters { diff --git a/packages/server/src/integrations/mongodb.ts b/packages/server/src/integrations/mongodb.ts index af7b49153..ab21da6fd 100644 --- a/packages/server/src/integrations/mongodb.ts +++ b/packages/server/src/integrations/mongodb.ts @@ -10,7 +10,7 @@ module MongoDBModule { interface MongoDBConfig { connectionString: string db: string - collection: string + // collection: string } const SCHEMA: Integration = { @@ -28,10 +28,6 @@ module MongoDBModule { type: DatasourceFieldTypes.STRING, required: true, }, - collection: { - type: DatasourceFieldTypes.STRING, - required: true, - }, }, query: { create: { @@ -40,7 +36,31 @@ module MongoDBModule { read: { type: QueryTypes.JSON, }, + update: { + type: QueryTypes.JSON, + }, + delete: { + type: QueryTypes.JSON, + } }, + extra: { + collection: { + displayName: "Collection", + type: DatasourceFieldTypes.STRING, + required: true, + }, + actionTypes: { + displayName: "Action Types", + type: DatasourceFieldTypes.LIST, + required: true, + data: { + read: ['find', 'findOne', 'findOneAndUpdate', "count", "distinct"], + create: ['insertOne', 'insertMany'], + update: ['updateOne', 'updateMany'], + delete: ['deleteOne', 'deleteMany'] + } + } + } } class MongoIntegration { @@ -56,12 +76,25 @@ module MongoDBModule { return this.client.connect() } - async create(query: { json: object }) { + async create(query: { json: object, extra: { [key: string]: string } }) { try { await this.connect() const db = this.client.db(this.config.db) - const collection = db.collection(this.config.collection) - return collection.insertOne(query.json) + const collection = db.collection(query.extra.collection) + + // For mongodb we add an extra actionType to specify + // which method we want to call on the collection + switch(query.extra.actionTypes) { + case 'insertOne': { + return collection.insertOne(query.json) + } + case 'insertMany': { + return collection.insertOne(query.json).toArray() + } + default: { + throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for create`) + } + } } catch (err) { console.error("Error writing to mongodb", err) throw err @@ -70,12 +103,32 @@ module MongoDBModule { } } - async read(query: { json: object }) { + async read(query: { json: object, extra: { [key: string]: string } }) { try { await this.connect() const db = this.client.db(this.config.db) - const collection = db.collection(this.config.collection) - return collection.find(query.json).toArray() + const collection = db.collection(query.extra.collection) + + switch(query.extra.actionTypes) { + case 'find': { + return collection.find(query.json).toArray() + } + case 'findOne': { + return collection.findOne(query.json) + } + case 'findOneAndUpdate': { + return collection.findOneAndUpdate(query.json) + } + case 'count': { + return collection.countDocuments(query.json) + } + case 'distinct': { + return collection.distinct(query.json) + } + default: { + throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for read`) + } + } } catch (err) { console.error("Error querying mongodb", err) throw err @@ -83,6 +136,56 @@ module MongoDBModule { await this.client.close() } } + + async update(query: { json: object, extra: { [key: string]: string } }) { + try { + await this.connect() + const db = this.client.db(this.config.db) + const collection = db.collection(query.extra.collection) + + switch(query.extra.actionTypes) { + case 'updateOne': { + return collection.updateOne(query.json) + } + case 'updateMany': { + return collection.updateMany(query.json).toArray() + } + default: { + throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for update`) + } + } + } catch (err) { + console.error("Error writing to mongodb", err) + throw err + } finally { + await this.client.close() + } + } + + async delete(query: { json: object, extra: { [key: string]: string } }) { + try { + await this.connect() + const db = this.client.db(this.config.db) + const collection = db.collection(query.extra.collection) + + switch(query.extra.actionTypes) { + case 'deleteOne': { + return collection.deleteOne(query.json) + } + case 'deleteMany': { + return collection.deleteMany(query.json).toArray() + } + default: { + throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for delete`) + } + } + } catch (err) { + console.error("Error writing to mongodb", err) + throw err + } finally { + await this.client.close() + } + } } module.exports = { From 4b48ea94e4ac377ee68184f3155c2bbea422fa79 Mon Sep 17 00:00:00 2001 From: Christo Date: Fri, 9 Jul 2021 20:08:26 +0200 Subject: [PATCH 002/173] Test: Add some basic tests for mongodb queries --- packages/server/__mocks__/mongodb.ts | 15 ++++++ .../src/integrations/tests/mongo.spec.js | 48 ++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/packages/server/__mocks__/mongodb.ts b/packages/server/__mocks__/mongodb.ts index d7d8f852c..06eefefea 100644 --- a/packages/server/__mocks__/mongodb.ts +++ b/packages/server/__mocks__/mongodb.ts @@ -5,11 +5,26 @@ module MongoMock { this.connect = jest.fn() this.close = jest.fn() this.insertOne = jest.fn() + this.insertMany = jest.fn(() => ({toArray: () => []})) this.find = jest.fn(() => ({toArray: () => []})) + this.findOne = jest.fn() + this.count = jest.fn() + this.deleteOne = jest.fn() + this.deleteMany = jest.fn(() => ({toArray: () => []})) + this.updateOne = jest.fn() + this.updateMany = jest.fn(() => ({toArray: () => []})) + this.collection = jest.fn(() => ({ insertOne: this.insertOne, find: this.find, + insertMany: this.insertMany, + findOne: this.findOne, + count: this.count, + deleteOne: this.deleteOne, + deleteMany: this.deleteMany, + updateOne: this.updateOne, + updateMany: this.updateMany, })) this.db = () => ({ diff --git a/packages/server/src/integrations/tests/mongo.spec.js b/packages/server/src/integrations/tests/mongo.spec.js index 1e37d5dd7..ce44617eb 100644 --- a/packages/server/src/integrations/tests/mongo.spec.js +++ b/packages/server/src/integrations/tests/mongo.spec.js @@ -8,6 +8,13 @@ class TestConfiguration { } } +function disableConsole() { + jest.spyOn(console, 'error'); + console.error.mockImplementation(() => {}); + + return console.error.mockRestore; +} + describe("MongoDB Integration", () => { let config let indexName = "Users" @@ -22,7 +29,8 @@ describe("MongoDB Integration", () => { } const response = await config.integration.create({ index: indexName, - json: body + json: body, + extra: { collection: 'testCollection', actionTypes: 'insertOne'} }) expect(config.integration.client.insertOne).toHaveBeenCalledWith(body) }) @@ -31,10 +39,46 @@ describe("MongoDB Integration", () => { const query = { json: { address: "test" - } + }, + extra: { collection: 'testCollection', actionTypes: 'find'} } const response = await config.integration.read(query) expect(config.integration.client.find).toHaveBeenCalledWith(query.json) expect(response).toEqual(expect.any(Array)) }) + + it("calls the delete method with the correct params", async () => { + const query = { + json: { + id: "test" + }, + extra: { collection: 'testCollection', actionTypes: 'deleteOne'} + } + const response = await config.integration.delete(query) + expect(config.integration.client.deleteOne).toHaveBeenCalledWith(query.json) + }) + + it("calls the update method with the correct params", async () => { + const query = { + json: { + id: "test" + }, + extra: { collection: 'testCollection', actionTypes: 'updateOne'} + } + const response = await config.integration.update(query) + expect(config.integration.client.updateOne).toHaveBeenCalledWith(query.json) + }) + + it("throws an error when an invalid query.extra.actionType is passed for each method", async () => { + const restore = disableConsole() + + const query = { + extra: { collection: 'testCollection', actionTypes: 'deleteOne'} + } + // Weird, need to do an IIFE for jest to recognize that it throws + expect(() => config.integration.read(query)()).toThrow(expect.any(Object)) + + restore() + }) + }) \ No newline at end of file From e296a67b500d62537f587340bb473a8dd353065b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 13 Jul 2021 17:27:04 +0100 Subject: [PATCH 003/173] Renaming groups to workspaces. --- packages/auth/src/constants.js | 2 +- packages/auth/src/db/utils.js | 46 +++++++++---------- .../server/src/automations/steps/serverLog.js | 2 +- .../worker/src/api/controllers/admin/auth.js | 4 +- .../src/api/controllers/admin/configs.js | 20 ++++---- .../worker/src/api/controllers/admin/email.js | 4 +- .../admin/{groups.js => workspaces.js} | 16 +++---- .../worker/src/api/routes/admin/configs.js | 2 +- packages/worker/src/api/routes/admin/email.js | 4 +- .../routes/admin/{groups.js => workspaces.js} | 14 +++--- packages/worker/src/api/routes/index.js | 4 +- .../api/routes/tests/utilities/controllers.js | 2 +- packages/worker/src/constants/index.js | 4 -- packages/worker/src/utilities/email.js | 20 ++++---- 14 files changed, 70 insertions(+), 74 deletions(-) rename packages/worker/src/api/controllers/admin/{groups.js => workspaces.js} (71%) rename packages/worker/src/api/routes/admin/{groups.js => workspaces.js} (68%) diff --git a/packages/auth/src/constants.js b/packages/auth/src/constants.js index 230c80b60..f96bea547 100644 --- a/packages/auth/src/constants.js +++ b/packages/auth/src/constants.js @@ -12,7 +12,7 @@ exports.GlobalRoles = { OWNER: "owner", ADMIN: "admin", BUILDER: "builder", - GROUP_MANAGER: "group_manager", + WORKSPACE_MANAGER: "workspace_manager", } exports.Configs = { diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 100dc005c..0e37ea03e 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -19,7 +19,7 @@ exports.StaticDatabases = { const DocumentTypes = { USER: "us", - GROUP: "group", + WORKSPACE: "workspace", CONFIG: "config", TEMPLATE: "template", APP: "app", @@ -61,21 +61,21 @@ function getDocParams(docType, docId = null, otherProps = {}) { } /** - * Generates a new group ID. - * @returns {string} The new group ID which the group doc can be stored under. + * Generates a new workspace ID. + * @returns {string} The new workspace ID which the workspace doc can be stored under. */ -exports.generateGroupID = () => { - return `${DocumentTypes.GROUP}${SEPARATOR}${newid()}` +exports.generateWorkspaceID = () => { + return `${DocumentTypes.WORKSPACE}${SEPARATOR}${newid()}` } /** - * Gets parameters for retrieving groups. + * Gets parameters for retrieving workspaces. */ -exports.getGroupParams = (id = "", otherProps = {}) => { +exports.getWorkspaceParams = (id = "", otherProps = {}) => { return { ...otherProps, - startkey: `${DocumentTypes.GROUP}${SEPARATOR}${id}`, - endkey: `${DocumentTypes.GROUP}${SEPARATOR}${id}${UNICODE_MAX}`, + startkey: `${DocumentTypes.WORKSPACE}${SEPARATOR}${id}`, + endkey: `${DocumentTypes.WORKSPACE}${SEPARATOR}${id}${UNICODE_MAX}`, } } @@ -103,14 +103,14 @@ exports.getGlobalUserParams = (globalId, otherProps = {}) => { /** * Generates a template ID. - * @param ownerId The owner/user of the template, this could be global or a group level. + * @param ownerId The owner/user of the template, this could be global or a workspace level. */ exports.generateTemplateID = ownerId => { return `${DocumentTypes.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}${newid()}` } /** - * Gets parameters for retrieving templates. Owner ID must be specified, either global or a group level. + * Gets parameters for retrieving templates. Owner ID must be specified, either global or a workspace level. */ exports.getTemplateParams = (ownerId, templateId, otherProps = {}) => { if (!templateId) { @@ -214,8 +214,8 @@ exports.dbExists = async (CouchDB, dbName) => { * Generates a new configuration ID. * @returns {string} The new configuration ID which the config doc can be stored under. */ -const generateConfigID = ({ type, group, user }) => { - const scope = [type, group, user].filter(Boolean).join(SEPARATOR) +const generateConfigID = ({ type, workspace, user }) => { + const scope = [type, workspace, user].filter(Boolean).join(SEPARATOR) return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}` } @@ -223,8 +223,8 @@ const generateConfigID = ({ type, group, user }) => { /** * Gets parameters for retrieving configurations. */ -const getConfigParams = ({ type, group, user }, otherProps = {}) => { - const scope = [type, group, user].filter(Boolean).join(SEPARATOR) +const getConfigParams = ({ type, workspace, user }, otherProps = {}) => { + const scope = [type, workspace, user].filter(Boolean).join(SEPARATOR) return { ...otherProps, @@ -234,15 +234,15 @@ const getConfigParams = ({ type, group, user }, otherProps = {}) => { } /** - * Returns the most granular configuration document from the DB based on the type, group and userID passed. + * Returns the most granular configuration document from the DB based on the type, workspace and userID passed. * @param {Object} db - db instance to query - * @param {Object} scopes - the type, group and userID scopes of the configuration. + * @param {Object} scopes - the type, workspace and userID scopes of the configuration. * @returns The most granular configuration document based on the scope. */ -const getScopedFullConfig = async function (db, { type, user, group }) { +const getScopedFullConfig = async function (db, { type, user, workspace }) { const response = await db.allDocs( getConfigParams( - { type, user, group }, + { type, user, workspace }, { include_docs: true, } @@ -252,14 +252,14 @@ const getScopedFullConfig = async function (db, { type, user, group }) { function determineScore(row) { const config = row.doc - // Config is specific to a user and a group - if (config._id.includes(generateConfigID({ type, user, group }))) { + // Config is specific to a user and a workspace + if (config._id.includes(generateConfigID({ type, user, workspace }))) { return 4 } else if (config._id.includes(generateConfigID({ type, user }))) { // Config is specific to a user only return 3 - } else if (config._id.includes(generateConfigID({ type, group }))) { - // Config is specific to a group only + } else if (config._id.includes(generateConfigID({ type, workspace }))) { + // Config is specific to a workspace only return 2 } else if (config._id.includes(generateConfigID({ type }))) { // Config is specific to a type only diff --git a/packages/server/src/automations/steps/serverLog.js b/packages/server/src/automations/steps/serverLog.js index c28309c5c..7389b65f5 100644 --- a/packages/server/src/automations/steps/serverLog.js +++ b/packages/server/src/automations/steps/serverLog.js @@ -19,7 +19,7 @@ module.exports.definition = { properties: { text: { type: "string", - title: "URL", + title: "Log", }, }, required: ["text"], diff --git a/packages/worker/src/api/controllers/admin/auth.js b/packages/worker/src/api/controllers/admin/auth.js index 0c2405b72..cc86f3232 100644 --- a/packages/worker/src/api/controllers/admin/auth.js +++ b/packages/worker/src/api/controllers/admin/auth.js @@ -102,7 +102,7 @@ exports.googlePreAuth = async (ctx, next) => { const db = new CouchDB(GLOBAL_DB) const config = await authPkg.db.getScopedConfig(db, { type: Configs.GOOGLE, - group: ctx.query.group, + workspace: ctx.query.workspace, }) const strategy = await google.strategyFactory(config) @@ -116,7 +116,7 @@ exports.googleAuth = async (ctx, next) => { const config = await authPkg.db.getScopedConfig(db, { type: Configs.GOOGLE, - group: ctx.query.group, + workspace: ctx.query.workspace, }) const strategy = await google.strategyFactory(config) diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/admin/configs.js index b93bd22c8..7e63592a3 100644 --- a/packages/worker/src/api/controllers/admin/configs.js +++ b/packages/worker/src/api/controllers/admin/configs.js @@ -16,13 +16,13 @@ const GLOBAL_DB = StaticDatabases.GLOBAL.name exports.save = async function (ctx) { const db = new CouchDB(GLOBAL_DB) - const { type, group, user, config } = ctx.request.body + const { type, workspace, user, config } = ctx.request.body // Config does not exist yet if (!ctx.request.body._id) { ctx.request.body._id = generateConfigID({ type, - group, + workspace, user, }) } @@ -65,17 +65,17 @@ exports.fetch = async function (ctx) { /** * Gets the most granular config for a particular configuration type. - * The hierarchy is type -> group -> user. + * The hierarchy is type -> workspace -> user. */ exports.find = async function (ctx) { const db = new CouchDB(GLOBAL_DB) - const { userId, groupId } = ctx.query - if (groupId && userId) { - const group = await db.get(groupId) - const userInGroup = group.users.some(groupUser => groupUser === userId) - if (!ctx.user.admin && !userInGroup) { - ctx.throw(400, `User is not in specified group: ${group}.`) + const { userId, workspaceId } = ctx.query + if (workspaceId && userId) { + const workspace = await db.get(workspaceId) + const userInWorkspace = workspace.users.some(workspaceUser => workspaceUser === userId) + if (!ctx.user.admin && !userInWorkspace) { + ctx.throw(400, `User is not in specified workspace: ${workspace}.`) } } @@ -84,7 +84,7 @@ exports.find = async function (ctx) { const scopedConfig = await getScopedFullConfig(db, { type: ctx.params.type, user: userId, - group: groupId, + workspace: workspaceId, }) if (scopedConfig) { diff --git a/packages/worker/src/api/controllers/admin/email.js b/packages/worker/src/api/controllers/admin/email.js index 6e16fd060..4e5719e9c 100644 --- a/packages/worker/src/api/controllers/admin/email.js +++ b/packages/worker/src/api/controllers/admin/email.js @@ -5,7 +5,7 @@ const authPkg = require("@budibase/auth") const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name exports.sendEmail = async ctx => { - const { groupId, email, userId, purpose, contents, from, subject } = + const { workspaceId, email, userId, purpose, contents, from, subject } = ctx.request.body let user if (userId) { @@ -13,7 +13,7 @@ exports.sendEmail = async ctx => { user = await db.get(userId) } const response = await sendEmail(email, purpose, { - groupId, + workspaceId, user, contents, from, diff --git a/packages/worker/src/api/controllers/admin/groups.js b/packages/worker/src/api/controllers/admin/workspaces.js similarity index 71% rename from packages/worker/src/api/controllers/admin/groups.js rename to packages/worker/src/api/controllers/admin/workspaces.js index 330fb3828..e99155ffb 100644 --- a/packages/worker/src/api/controllers/admin/groups.js +++ b/packages/worker/src/api/controllers/admin/workspaces.js @@ -1,20 +1,20 @@ const CouchDB = require("../../../db") -const { getGroupParams, generateGroupID, StaticDatabases } = +const { getWorkspaceParams, generateWorkspaceID, StaticDatabases } = require("@budibase/auth").db const GLOBAL_DB = StaticDatabases.GLOBAL.name exports.save = async function (ctx) { const db = new CouchDB(GLOBAL_DB) - const groupDoc = ctx.request.body + const workspaceDoc = ctx.request.body - // Group does not exist yet - if (!groupDoc._id) { - groupDoc._id = generateGroupID() + // workspace does not exist yet + if (!workspaceDoc._id) { + workspaceDoc._id = generateWorkspaceID() } try { - const response = await db.post(groupDoc) + const response = await db.post(workspaceDoc) ctx.body = { _id: response.id, _rev: response.rev, @@ -27,7 +27,7 @@ exports.save = async function (ctx) { exports.fetch = async function (ctx) { const db = new CouchDB(GLOBAL_DB) const response = await db.allDocs( - getGroupParams(undefined, { + getWorkspaceParams(undefined, { include_docs: true, }) ) @@ -49,7 +49,7 @@ exports.destroy = async function (ctx) { try { await db.remove(id, rev) - ctx.body = { message: "Group deleted successfully" } + ctx.body = { message: "Workspace deleted successfully" } } catch (err) { ctx.throw(err.status, err) } diff --git a/packages/worker/src/api/routes/admin/configs.js b/packages/worker/src/api/routes/admin/configs.js index 8056ad8cb..88764c634 100644 --- a/packages/worker/src/api/routes/admin/configs.js +++ b/packages/worker/src/api/routes/admin/configs.js @@ -46,7 +46,7 @@ function buildConfigSaveValidation() { return joiValidator.body(Joi.object({ _id: Joi.string().optional(), _rev: Joi.string().optional(), - group: Joi.string().optional(), + workspace: Joi.string().optional(), type: Joi.string().valid(...Object.values(Configs)).required(), config: Joi.alternatives() .conditional("type", { diff --git a/packages/worker/src/api/routes/admin/email.js b/packages/worker/src/api/routes/admin/email.js index a36dc5de9..0412f9c7c 100644 --- a/packages/worker/src/api/routes/admin/email.js +++ b/packages/worker/src/api/routes/admin/email.js @@ -12,8 +12,8 @@ function buildEmailSendValidation() { return joiValidator.body(Joi.object({ email: Joi.string().email(), purpose: Joi.string().valid(...Object.values(EmailTemplatePurpose)), - groupId: Joi.string().allow("", null), - fromt: Joi.string().allow("", null), + workspaceId: Joi.string().allow("", null), + from: Joi.string().allow("", null), contents: Joi.string().allow("", null), subject: Joi.string().allow("", null), }).required().unknown(true)) diff --git a/packages/worker/src/api/routes/admin/groups.js b/packages/worker/src/api/routes/admin/workspaces.js similarity index 68% rename from packages/worker/src/api/routes/admin/groups.js rename to packages/worker/src/api/routes/admin/workspaces.js index 4611e6707..72c3593ed 100644 --- a/packages/worker/src/api/routes/admin/groups.js +++ b/packages/worker/src/api/routes/admin/workspaces.js @@ -1,12 +1,12 @@ const Router = require("@koa/router") -const controller = require("../../controllers/admin/groups") +const controller = require("../../controllers/admin/workspaces") const joiValidator = require("../../../middleware/joi-validator") const adminOnly = require("../../../middleware/adminOnly") const Joi = require("joi") const router = Router() -function buildGroupSaveValidation() { +function buildWorkspaceSaveValidation() { // prettier-ignore return joiValidator.body(Joi.object({ _id: Joi.string().optional(), @@ -26,13 +26,13 @@ function buildGroupSaveValidation() { router .post( - "/api/admin/groups", + "/api/admin/workspaces", adminOnly, - buildGroupSaveValidation(), + buildWorkspaceSaveValidation(), controller.save ) - .get("/api/admin/groups", controller.fetch) - .delete("/api/admin/groups/:id", adminOnly, controller.destroy) - .get("/api/admin/groups/:id", controller.find) + .delete("/api/admin/workspaces/:id", adminOnly, controller.destroy) + .get("/api/admin/workspaces", controller.fetch) + .get("/api/admin/workspaces/:id", controller.find) module.exports = router diff --git a/packages/worker/src/api/routes/index.js b/packages/worker/src/api/routes/index.js index 21ec32488..64988c4dc 100644 --- a/packages/worker/src/api/routes/index.js +++ b/packages/worker/src/api/routes/index.js @@ -1,6 +1,6 @@ const userRoutes = require("./admin/users") const configRoutes = require("./admin/configs") -const groupRoutes = require("./admin/groups") +const workspaceRoutes = require("./admin/workspaces") const templateRoutes = require("./admin/templates") const emailRoutes = require("./admin/email") const authRoutes = require("./admin/auth") @@ -11,7 +11,7 @@ const appRoutes = require("./app") exports.routes = [ configRoutes, userRoutes, - groupRoutes, + workspaceRoutes, authRoutes, appRoutes, templateRoutes, diff --git a/packages/worker/src/api/routes/tests/utilities/controllers.js b/packages/worker/src/api/routes/tests/utilities/controllers.js index b0d2441c0..869cf96d5 100644 --- a/packages/worker/src/api/routes/tests/utilities/controllers.js +++ b/packages/worker/src/api/routes/tests/utilities/controllers.js @@ -1,6 +1,6 @@ module.exports = { email: require("../../../controllers/admin/email"), - groups: require("../../../controllers/admin/groups"), + workspaces: require("../../../controllers/admin/workspaces"), config: require("../../../controllers/admin/configs"), templates: require("../../../controllers/admin/templates"), users: require("../../../controllers/admin/users"), diff --git a/packages/worker/src/constants/index.js b/packages/worker/src/constants/index.js index b40446cc8..fb7c38a10 100644 --- a/packages/worker/src/constants/index.js +++ b/packages/worker/src/constants/index.js @@ -8,10 +8,6 @@ exports.UserStatus = { INACTIVE: "inactive", } -exports.Groups = { - ALL_USERS: "all_users", -} - exports.Configs = Configs exports.ConfigUploads = { diff --git a/packages/worker/src/utilities/email.js b/packages/worker/src/utilities/email.js index dc13bb948..d0441d552 100644 --- a/packages/worker/src/utilities/email.js +++ b/packages/worker/src/utilities/email.js @@ -101,31 +101,31 @@ async function buildEmail(purpose, email, context, { user, contents } = {}) { /** * Utility function for finding most valid SMTP configuration. * @param {object} db The CouchDB database which is to be looked up within. - * @param {string|null} groupId If using finer grain control of configs a group can be used. + * @param {string|null} workspaceId If using finer grain control of configs a workspace can be used. * @return {Promise} returns the SMTP configuration if it exists */ -async function getSmtpConfiguration(db, groupId = null) { +async function getSmtpConfiguration(db, workspaceId = null) { const params = { type: Configs.SMTP, } - if (groupId) { - params.group = groupId + if (workspaceId) { + params.workspace = workspaceId } return getScopedConfig(db, params) } /** * Checks if a SMTP config exists based on passed in parameters. - * @param groupId + * @param workspaceId * @return {Promise} returns true if there is a configuration that can be used. */ -exports.isEmailConfigured = async (groupId = null) => { +exports.isEmailConfigured = async (workspaceId = null) => { // when "testing" simply return true if (TEST_MODE) { return true } const db = new CouchDB(GLOBAL_DB) - const config = await getSmtpConfiguration(db, groupId) + const config = await getSmtpConfiguration(db, workspaceId) return config != null } @@ -134,7 +134,7 @@ exports.isEmailConfigured = async (groupId = null) => { * send an email using it. * @param {string} email The email address to send to. * @param {string} purpose The purpose of the email being sent (e.g. reset password). - * @param {string|undefined} groupId If finer grain controls being used then this will lookup config for group. + * @param {string|undefined} workspaceId If finer grain controls being used then this will lookup config for workspace. * @param {object|undefined} user If sending to an existing user the object can be provided, this is used in the context. * @param {string|undefined} from If sending from an address that is not what is configured in the SMTP config. * @param {string|undefined} contents If sending a custom email then can supply contents which will be added to it. @@ -146,10 +146,10 @@ exports.isEmailConfigured = async (groupId = null) => { exports.sendEmail = async ( email, purpose, - { groupId, user, from, contents, subject, info } = {} + { workspaceId, user, from, contents, subject, info } = {} ) => { const db = new CouchDB(GLOBAL_DB) - let config = (await getSmtpConfiguration(db, groupId)) || {} + let config = (await getSmtpConfiguration(db, workspaceId)) || {} if (Object.keys(config).length === 0 && !TEST_MODE) { throw "Unable to find SMTP configuration." } From d17456bacf914d135463bbbc39c65b7dc974086f Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 13 Jul 2021 17:28:05 +0100 Subject: [PATCH 004/173] Linting. --- packages/worker/src/api/controllers/admin/configs.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/admin/configs.js index 7e63592a3..55d2df6ee 100644 --- a/packages/worker/src/api/controllers/admin/configs.js +++ b/packages/worker/src/api/controllers/admin/configs.js @@ -73,7 +73,9 @@ exports.find = async function (ctx) { const { userId, workspaceId } = ctx.query if (workspaceId && userId) { const workspace = await db.get(workspaceId) - const userInWorkspace = workspace.users.some(workspaceUser => workspaceUser === userId) + const userInWorkspace = workspace.users.some( + workspaceUser => workspaceUser === userId + ) if (!ctx.user.admin && !userInWorkspace) { ctx.throw(400, `User is not in specified workspace: ${workspace}.`) } From f2c2ead3fcdfeb116f37daf882a34a2ce9d3b94b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 14 Jul 2021 16:29:32 +0100 Subject: [PATCH 005/173] Cleaning up deployments into the app DB, moving API keys to be in global DB (which will be tenancy managed) and adding concept of platform info DB. --- packages/auth/src/db/utils.js | 8 +++- .../server/src/api/controllers/apikeys.js | 32 ++++++++++++++-- .../src/api/controllers/deploy/index.js | 21 +++++----- packages/server/src/db/builder.js | 38 ------------------- packages/server/src/db/utils.js | 9 +---- 5 files changed, 48 insertions(+), 60 deletions(-) delete mode 100644 packages/server/src/db/builder.js diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 0e37ea03e..a39166a53 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -11,9 +11,13 @@ exports.ViewNames = { exports.StaticDatabases = { GLOBAL: { name: "global-db", + docs: { + apiKeys: "apikeys", + }, }, - DEPLOYMENTS: { - name: "deployments", + // contains information about tenancy and so on + PLATFORM_INFO: { + name: "global-info", }, } diff --git a/packages/server/src/api/controllers/apikeys.js b/packages/server/src/api/controllers/apikeys.js index 55422ee60..9c1acfbc2 100644 --- a/packages/server/src/api/controllers/apikeys.js +++ b/packages/server/src/api/controllers/apikeys.js @@ -1,8 +1,32 @@ -const builderDB = require("../../db/builder") +const CouchDB = require("../../db") +const { StaticDatabases } = require("@budibase/auth/db") + +const GLOBAL_DB = StaticDatabases.GLOBAL.name +const KEYS_DOC = StaticDatabases.GLOBAL.docs.apiKeys + +async function getBuilderMainDoc() { + const db = new CouchDB(GLOBAL_DB) + try { + return await db.get(KEYS_DOC) + } catch (err) { + // doesn't exist yet, nothing to get + return { + _id: KEYS_DOC, + } + } +} + +async function setBuilderMainDoc(doc) { + // make sure to override the ID + doc._id = KEYS_DOC + const db = new CouchDB(GLOBAL_DB) + return db.put(doc) +} + exports.fetch = async function (ctx) { try { - const mainDoc = await builderDB.getBuilderMainDoc() + const mainDoc = await getBuilderMainDoc() ctx.body = mainDoc.apiKeys ? mainDoc.apiKeys : {} } catch (err) { /* istanbul ignore next */ @@ -15,12 +39,12 @@ exports.update = async function (ctx) { const value = ctx.request.body.value try { - const mainDoc = await builderDB.getBuilderMainDoc() + const mainDoc = await getBuilderMainDoc() if (mainDoc.apiKeys == null) { mainDoc.apiKeys = {} } mainDoc.apiKeys[key] = value - const resp = await builderDB.setBuilderMainDoc(mainDoc) + const resp = await setBuilderMainDoc(mainDoc) ctx.body = { _id: resp.id, _rev: resp.rev, diff --git a/packages/server/src/api/controllers/deploy/index.js b/packages/server/src/api/controllers/deploy/index.js index f5db81e6e..4125f0443 100644 --- a/packages/server/src/api/controllers/deploy/index.js +++ b/packages/server/src/api/controllers/deploy/index.js @@ -1,6 +1,6 @@ -const PouchDB = require("../../../db") +const CouchDB = require("../../../db") const Deployment = require("./Deployment") -const { Replication, StaticDatabases } = require("@budibase/auth/db") +const { Replication } = require("@budibase/auth/db") const { DocumentTypes } = require("../../../db/utils") // the max time we can wait for an invalidation to complete before considering it failed @@ -31,11 +31,12 @@ async function checkAllDeployments(deployments) { async function storeDeploymentHistory(deployment) { const appId = deployment.getAppId() const deploymentJSON = deployment.getJSON() - const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name) + const db = new CouchDB(appId) let deploymentDoc try { - deploymentDoc = await db.get(appId) + // theres only one deployment doc per app database + deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS) } catch (err) { deploymentDoc = { _id: appId, history: {} } } @@ -67,7 +68,7 @@ async function deployApp(deployment) { }) await replication.replicate() - const db = new PouchDB(productionAppId) + const db = new CouchDB(productionAppId) const appDoc = await db.get(DocumentTypes.APP_METADATA) appDoc.appId = productionAppId appDoc.instance._id = productionAppId @@ -98,8 +99,9 @@ async function deployApp(deployment) { exports.fetchDeployments = async function (ctx) { try { - const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name) - const deploymentDoc = await db.get(ctx.appId) + const appId = ctx.appId + const db = new CouchDB(appId) + const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS) const { updated, deployments } = await checkAllDeployments( deploymentDoc, ctx.user @@ -115,8 +117,9 @@ exports.fetchDeployments = async function (ctx) { exports.deploymentProgress = async function (ctx) { try { - const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name) - const deploymentDoc = await db.get(ctx.appId) + const appId = ctx.appId + const db = new CouchDB(appId) + const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS) ctx.body = deploymentDoc[ctx.params.deploymentId] } catch (err) { ctx.throw( diff --git a/packages/server/src/db/builder.js b/packages/server/src/db/builder.js deleted file mode 100644 index d2bbcd404..000000000 --- a/packages/server/src/db/builder.js +++ /dev/null @@ -1,38 +0,0 @@ -const CouchDB = require("./index") -const { StaticDatabases } = require("./utils") -const env = require("../environment") - -const SELF_HOST_ERR = "Unable to access builder DB/doc - not self hosted." -const BUILDER_DB = StaticDatabases.BUILDER - -/** - * This is the builder database, right now this is a single, static database - * that is present across the whole system and determines some core functionality - * for the builder (e.g. storage of API keys). This has been limited to self hosting - * as it doesn't make as much sense against the currently design Cloud system. - */ - -exports.getBuilderMainDoc = async () => { - if (!env.SELF_HOSTED) { - throw SELF_HOST_ERR - } - const db = new CouchDB(BUILDER_DB.name) - try { - return await db.get(BUILDER_DB.baseDoc) - } catch (err) { - // doesn't exist yet, nothing to get - return { - _id: BUILDER_DB.baseDoc, - } - } -} - -exports.setBuilderMainDoc = async doc => { - if (!env.SELF_HOSTED) { - throw SELF_HOST_ERR - } - // make sure to override the ID - doc._id = BUILDER_DB.baseDoc - const db = new CouchDB(BUILDER_DB.name) - return db.put(doc) -} diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 74ddf8717..eb8c32bb5 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -34,6 +34,7 @@ const DocumentTypes = { DATASOURCE: "datasource", DATASOURCE_PLUS: "datasource_plus", QUERY: "query", + DEPLOYMENTS: "deployments", } const ViewNames = { @@ -49,13 +50,7 @@ const SearchIndexes = { ROWS: "rows", } -exports.StaticDatabases = { - BUILDER: { - name: "builder-db", - baseDoc: "builder-doc", - }, - ...StaticDatabases, -} +exports.StaticDatabases = StaticDatabases const BudibaseInternalDB = { _id: "bb_internal", From 6aaef0e230018e99d69f0b9a833efcf09fb33686 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 14 Jul 2021 17:51:35 +0100 Subject: [PATCH 006/173] Fixing deployment issue. --- packages/server/src/api/controllers/deploy/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/deploy/index.js b/packages/server/src/api/controllers/deploy/index.js index 4125f0443..4608ca634 100644 --- a/packages/server/src/api/controllers/deploy/index.js +++ b/packages/server/src/api/controllers/deploy/index.js @@ -38,7 +38,7 @@ async function storeDeploymentHistory(deployment) { // theres only one deployment doc per app database deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS) } catch (err) { - deploymentDoc = { _id: appId, history: {} } + deploymentDoc = { _id: DocumentTypes.DEPLOYMENTS, history: {} } } const deploymentId = deploymentJSON._id From 5ffe3c7935dc71f8b4f1b12c19759ab0190b1594 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 15 Jul 2021 17:57:02 +0100 Subject: [PATCH 007/173] First version of multi-tenancy, work still to be done. --- packages/auth/src/cache/user.js | 11 +- packages/auth/src/db/utils.js | 23 ++++ packages/auth/src/db/views.js | 6 +- packages/auth/src/index.js | 6 +- packages/auth/src/middleware/authenticated.js | 2 +- .../auth/src/middleware/passport/google.js | 13 +-- .../auth/src/middleware/passport/local.js | 4 +- packages/auth/src/security/sessions.js | 7 +- packages/auth/src/utils.js | 21 +++- .../server/src/api/controllers/apikeys.js | 17 ++- .../server/src/api/controllers/application.js | 2 +- packages/server/src/api/controllers/auth.js | 2 +- .../server/src/api/controllers/automation.js | 1 + packages/server/src/api/controllers/user.js | 2 +- .../src/automations/steps/sendSmtpEmail.js | 4 +- packages/server/src/automations/thread.js | 2 + packages/server/src/db/linkedRows/index.js | 11 +- .../src/tests/utilities/TestConfiguration.js | 7 +- packages/server/src/utilities/global.js | 14 +-- packages/server/src/utilities/rowProcessor.js | 5 +- packages/server/src/utilities/users.js | 2 +- .../server/src/utilities/workerRequests.js | 15 +-- .../worker/src/api/controllers/admin/auth.js | 17 ++- .../src/api/controllers/admin/configs.js | 28 ++--- .../worker/src/api/controllers/admin/email.js | 14 +-- .../src/api/controllers/admin/templates.js | 17 ++- .../worker/src/api/controllers/admin/users.js | 108 ++++++++++++------ .../src/api/controllers/admin/workspaces.js | 15 +-- packages/worker/src/api/routes/admin/auth.js | 8 +- .../worker/src/constants/templates/index.js | 11 +- packages/worker/src/utilities/email.js | 15 ++- packages/worker/src/utilities/templates.js | 7 +- 32 files changed, 243 insertions(+), 174 deletions(-) diff --git a/packages/auth/src/cache/user.js b/packages/auth/src/cache/user.js index 46202cbfe..b49721a54 100644 --- a/packages/auth/src/cache/user.js +++ b/packages/auth/src/cache/user.js @@ -1,15 +1,18 @@ -const { getDB } = require("../db") -const { StaticDatabases } = require("../db/utils") +const { getGlobalDB } = require("../db/utils") const redis = require("../redis/authRedis") +const { lookupTenantId } = require("../utils") const EXPIRY_SECONDS = 3600 -exports.getUser = async userId => { +exports.getUser = async (userId, tenantId = null) => { + if (!tenantId) { + tenantId = await lookupTenantId({ userId }) + } const client = await redis.getUserClient() // try cache let user = await client.get(userId) if (!user) { - user = await getDB(StaticDatabases.GLOBAL.name).get(userId) + user = await getGlobalDB(tenantId).get(userId) client.store(userId, user, EXPIRY_SECONDS) } return user diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index a39166a53..f305d18d0 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -1,5 +1,6 @@ const { newid } = require("../hashing") const Replication = require("./Replication") +const { getDB } = require("./index") const UNICODE_MAX = "\ufff0" const SEPARATOR = "_" @@ -18,6 +19,9 @@ exports.StaticDatabases = { // contains information about tenancy and so on PLATFORM_INFO: { name: "global-info", + docs: { + tenants: "tenants", + }, }, } @@ -64,6 +68,25 @@ function getDocParams(docType, docId = null, otherProps = {}) { } } +/** + * Gets the name of the global DB to connect to in a multi-tenancy system. + */ +exports.getGlobalDB = tenantId => { + const globalName = exports.StaticDatabases.GLOBAL.name + // fallback for system pre multi-tenancy + if (!tenantId) { + return globalName + } + return getDB(`${tenantId}${SEPARATOR}${globalName}`) +} + +/** + * Given a koa context this tries to find the correct tenant Global DB. + */ +exports.getGlobalDBFromCtx = ctx => { + return exports.getGlobalDB(ctx.user.tenantId) +} + /** * 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/db/views.js b/packages/auth/src/db/views.js index 1f1f28b91..1b48786e2 100644 --- a/packages/auth/src/db/views.js +++ b/packages/auth/src/db/views.js @@ -1,5 +1,4 @@ -const { DocumentTypes, ViewNames, StaticDatabases } = require("./utils") -const { getDB } = require("./index") +const { DocumentTypes, ViewNames } = require("./utils") function DesignDoc() { return { @@ -10,8 +9,7 @@ function DesignDoc() { } } -exports.createUserEmailView = async () => { - const db = getDB(StaticDatabases.GLOBAL.name) +exports.createUserEmailView = async db => { let designDoc try { designDoc = await db.get("_design/database") diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index ff604b5a3..2e398d8c5 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -1,9 +1,9 @@ const passport = require("koa-passport") const LocalStrategy = require("passport-local").Strategy const JwtStrategy = require("passport-jwt").Strategy -const { StaticDatabases } = require("./db/utils") +const { getGlobalDB } = require("./db/utils") const { jwt, local, authenticated, google, auditLog } = require("./middleware") -const { setDB, getDB } = require("./db") +const { setDB } = require("./db") const userCache = require("./cache/user") // Strategies @@ -13,7 +13,7 @@ passport.use(new JwtStrategy(jwt.options, jwt.authenticate)) passport.serializeUser((user, done) => done(null, user)) passport.deserializeUser(async (user, done) => { - const db = getDB(StaticDatabases.GLOBAL.name) + const db = getGlobalDB(user.tenantId) try { const user = await db.get(user._id) diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index db1fdfacd..ebdf328cf 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -56,7 +56,7 @@ module.exports = (noAuthPatterns = [], opts) => { error = "No session found" } else { try { - user = await getUser(userId) + user = await getUser(userId, session.tenantId) delete user.password authenticated = true } catch (err) { diff --git a/packages/auth/src/middleware/passport/google.js b/packages/auth/src/middleware/passport/google.js index b357eb490..bc6812157 100644 --- a/packages/auth/src/middleware/passport/google.js +++ b/packages/auth/src/middleware/passport/google.js @@ -1,23 +1,22 @@ const env = require("../../environment") const jwt = require("jsonwebtoken") -const database = require("../../db") const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy const { - StaticDatabases, generateGlobalUserID, + getGlobalDB, ViewNames, } = require("../../db/utils") const { newid } = require("../../hashing") const { createASession } = require("../../security/sessions") +const { lookupTenantId } = require("../../utils") async function authenticate(token, tokenSecret, profile, done) { // Check the user exists in the instance DB by email - const db = database.getDB(StaticDatabases.GLOBAL.name) - - let dbUser - const userId = generateGlobalUserID(profile.id) + const tenantId = await lookupTenantId({ userId }) + const db = getGlobalDB(tenantId) + let dbUser try { // use the google profile id dbUser = await db.get(userId) @@ -62,7 +61,7 @@ async function authenticate(token, tokenSecret, profile, done) { // authenticate const sessionId = newid() - await createASession(dbUser._id, sessionId) + await createASession(dbUser._id, { sessionId, tenantId: dbUser.tenantId }) dbUser.token = jwt.sign( { diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js index 90303cb95..147305e31 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/auth/src/middleware/passport/local.js @@ -34,12 +34,14 @@ exports.authenticate = async function (email, password, done) { // authenticate if (await compare(password, dbUser.password)) { const sessionId = newid() - await createASession(dbUser._id, sessionId) + const tenantId = dbUser.tenantId + await createASession(dbUser._id, { sessionId, tenantId }) dbUser.token = jwt.sign( { userId: dbUser._id, sessionId, + tenantId, }, env.JWT_SECRET ) diff --git a/packages/auth/src/security/sessions.js b/packages/auth/src/security/sessions.js index 4051df712..328f74c79 100644 --- a/packages/auth/src/security/sessions.js +++ b/packages/auth/src/security/sessions.js @@ -12,12 +12,13 @@ function makeSessionID(userId, sessionId) { return `${userId}/${sessionId}` } -exports.createASession = async (userId, sessionId) => { +exports.createASession = async (userId, session) => { const client = await redis.getSessionClient() - const session = { + const sessionId = session.sessionId + session = { createdAt: new Date().toISOString(), lastAccessedAt: new Date().toISOString(), - sessionId, + ...session, userId, } await client.store(makeSessionID(userId, sessionId), session, EXPIRY_SECONDS) diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index 8bd635e2e..b5225881d 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -8,6 +8,7 @@ const jwt = require("jsonwebtoken") const { options } = require("./middleware/passport/jwt") const { createUserEmailView } = require("./db/views") const { getDB } = require("./db") +const { getGlobalDB } = require("./db/utils") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -100,17 +101,31 @@ exports.isClient = ctx => { return ctx.headers["x-budibase-type"] === "client" } +exports.lookupTenantId = async ({ email, userId }) => { + const toQuery = email || userId + const db = getDB(StaticDatabases.PLATFORM_INFO.name) + const doc = await db.get(toQuery) + if (!doc || !doc.tenantId) { + throw "Unable to find tenant" + } + return doc.tenantId +} + /** * Given an email address this will use a view to search through * all the users to find one with this email address. * @param {string} email the email to lookup the user by. + * @param {string|null} tenantId If tenant ID is known it can be specified * @return {Promise} */ -exports.getGlobalUserByEmail = async email => { +exports.getGlobalUserByEmail = async (email, tenantId = null) => { if (email == null) { throw "Must supply an email address to view" } - const db = getDB(StaticDatabases.GLOBAL.name) + if (!tenantId) { + tenantId = await exports.lookupTenantId({ email }) + } + const db = getGlobalDB(tenantId) try { let users = ( await db.query(`database/${ViewNames.USER_BY_EMAIL}`, { @@ -122,7 +137,7 @@ exports.getGlobalUserByEmail = async email => { return users.length <= 1 ? users[0] : users } catch (err) { if (err != null && err.name === "not_found") { - await createUserEmailView() + await createUserEmailView(db) return exports.getGlobalUserByEmail(email) } else { throw err diff --git a/packages/server/src/api/controllers/apikeys.js b/packages/server/src/api/controllers/apikeys.js index 9c1acfbc2..98dee4699 100644 --- a/packages/server/src/api/controllers/apikeys.js +++ b/packages/server/src/api/controllers/apikeys.js @@ -1,11 +1,10 @@ const CouchDB = require("../../db") -const { StaticDatabases } = require("@budibase/auth/db") +const { StaticDatabases, getGlobalDBFromCtx } = require("@budibase/auth/db") -const GLOBAL_DB = StaticDatabases.GLOBAL.name const KEYS_DOC = StaticDatabases.GLOBAL.docs.apiKeys -async function getBuilderMainDoc() { - const db = new CouchDB(GLOBAL_DB) +async function getBuilderMainDoc(ctx) { + const db = getGlobalDBFromCtx(ctx) try { return await db.get(KEYS_DOC) } catch (err) { @@ -16,17 +15,17 @@ async function getBuilderMainDoc() { } } -async function setBuilderMainDoc(doc) { +async function setBuilderMainDoc(ctx, doc) { // make sure to override the ID doc._id = KEYS_DOC - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) return db.put(doc) } exports.fetch = async function (ctx) { try { - const mainDoc = await getBuilderMainDoc() + const mainDoc = await getBuilderMainDoc(ctx) ctx.body = mainDoc.apiKeys ? mainDoc.apiKeys : {} } catch (err) { /* istanbul ignore next */ @@ -39,12 +38,12 @@ exports.update = async function (ctx) { const value = ctx.request.body.value try { - const mainDoc = await getBuilderMainDoc() + const mainDoc = await getBuilderMainDoc(ctx) if (mainDoc.apiKeys == null) { mainDoc.apiKeys = {} } mainDoc.apiKeys[key] = value - const resp = await setBuilderMainDoc(mainDoc) + const resp = await setBuilderMainDoc(ctx, mainDoc) ctx.body = { _id: resp.id, _rev: resp.rev, diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index a2e254461..c01d43c86 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -295,7 +295,7 @@ exports.delete = async function (ctx) { await deleteApp(ctx.params.appId) } // make sure the app/role doesn't stick around after the app has been deleted - await removeAppFromUserRoles(ctx.params.appId) + await removeAppFromUserRoles(ctx, ctx.params.appId) ctx.status = 200 ctx.body = result diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index da863f549..5078218fc 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -22,7 +22,7 @@ exports.fetchSelf = async ctx => { const userTable = await db.get(InternalTables.USER_METADATA) const metadata = await db.get(userId) // specifically needs to make sure is enriched - ctx.body = await outputProcessing(appId, userTable, { + ctx.body = await outputProcessing(ctx, userTable, { ...user, ...metadata, }) diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 2d164b415..c54a6803f 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -159,6 +159,7 @@ exports.create = async function (ctx) { automation._id = generateAutomationID() + automation.tenantId = ctx.user.tenantId automation.type = "automation" automation = cleanAutomationInputs(automation) automation = await checkForWebhooks({ diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 6778f983c..935ead38b 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -17,7 +17,7 @@ function removeGlobalProps(user) { exports.fetchMetadata = async function (ctx) { const database = new CouchDB(ctx.appId) - const global = await getGlobalUsers(ctx.appId) + const global = await getGlobalUsers(ctx, ctx.appId) const metadata = ( await database.allDocs( getUserMetadataParams(null, { diff --git a/packages/server/src/automations/steps/sendSmtpEmail.js b/packages/server/src/automations/steps/sendSmtpEmail.js index 764972b40..7b25da801 100644 --- a/packages/server/src/automations/steps/sendSmtpEmail.js +++ b/packages/server/src/automations/steps/sendSmtpEmail.js @@ -46,13 +46,13 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs }) { +module.exports.run = async function ({ inputs, tenantId }) { let { to, from, subject, contents } = inputs if (!contents) { contents = "

No content

" } try { - let response = await sendSmtpEmail(to, from, subject, contents) + let response = await sendSmtpEmail(tenantId, to, from, subject, contents) return { success: true, response, diff --git a/packages/server/src/automations/thread.js b/packages/server/src/automations/thread.js index 7b6d969a9..a676afc04 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -22,6 +22,7 @@ class Orchestrator { // step zero is never used as the template string is zero indexed for customer facing this._context = { steps: [{}], trigger: triggerOutput } this._automation = automation + this._tenantId = automation.tenantId // create an emitter which has the chain count for this automation run in it, so it can block // excessive chaining if required this._emitter = new AutomationEmitter(this._chainCount + 1) @@ -57,6 +58,7 @@ class Orchestrator { apiKey: automation.apiKey, emitter: this._emitter, context: this._context, + tenantId: this._tenantId, }) if (step.stepId === FILTER_STEP_ID && !outputs.success) { break diff --git a/packages/server/src/db/linkedRows/index.js b/packages/server/src/db/linkedRows/index.js index 754340046..dece78dcf 100644 --- a/packages/server/src/db/linkedRows/index.js +++ b/packages/server/src/db/linkedRows/index.js @@ -60,7 +60,7 @@ async function getLinksForRows(appId, rows) { ) } -async function getFullLinkedDocs(appId, links) { +async function getFullLinkedDocs(ctx, appId, links) { // create DBs const db = new CouchDB(appId) const linkedRowIds = links.map(link => link.id) @@ -71,7 +71,7 @@ async function getFullLinkedDocs(appId, links) { let [users, other] = partition(linked, linkRow => linkRow._id.startsWith(USER_METDATA_PREFIX) ) - const globalUsers = await getGlobalUsers(appId, users) + const globalUsers = await getGlobalUsers(ctx, appId, users) users = users.map(user => { const globalUser = globalUsers.find( globalUser => globalUser && user._id.includes(globalUser._id) @@ -166,12 +166,13 @@ exports.attachLinkIDs = async (appId, rows) => { /** * Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row. * This is required for formula fields, this may only be utilised internally (for now). - * @param {string} appId The app in which the tables/rows/links exist. + * @param {object} ctx The request which is looking for rows. * @param {object} table The table from which the rows originated. * @param {array} rows The rows which are to be enriched. * @return {Promise<*>} returns the rows with all of the enriched relationships on it. */ -exports.attachFullLinkedDocs = async (appId, table, rows) => { +exports.attachFullLinkedDocs = async (ctx, table, rows) => { + const appId = ctx.appId const linkedTableIds = getLinkedTableIDs(table) if (linkedTableIds.length === 0) { return rows @@ -182,7 +183,7 @@ exports.attachFullLinkedDocs = async (appId, table, rows) => { const links = (await getLinksForRows(appId, rows)).filter(link => rows.some(row => row._id === link.thisId) ) - let linked = await getFullLinkedDocs(appId, links) + let linked = await getFullLinkedDocs(ctx, appId, links) const linkedTables = [] for (let row of rows) { for (let link of links.filter(link => link.thisId === row._id)) { diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index a69ea3538..3a883b4a7 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -16,14 +16,14 @@ const supertest = require("supertest") const { cleanup } = require("../../utilities/fileSystem") const { Cookies } = require("@budibase/auth").constants const { jwt } = require("@budibase/auth").auth -const { StaticDatabases } = require("@budibase/auth/db") +const { getGlobalDB } = require("@budibase/auth/db") const { createASession } = require("@budibase/auth/sessions") const { user: userCache } = require("@budibase/auth/cache") -const CouchDB = require("../../db") const GLOBAL_USER_ID = "us_uuid1" const EMAIL = "babs@babs.com" const PASSWORD = "babs_password" +const TENANT_ID = "tenant1" class TestConfiguration { constructor(openServer = true) { @@ -65,7 +65,7 @@ class TestConfiguration { } async globalUser(id = GLOBAL_USER_ID, builder = true, roles) { - const db = new CouchDB(StaticDatabases.GLOBAL.name) + const db = getGlobalDB(TENANT_ID) let existing try { existing = await db.get(id) @@ -76,6 +76,7 @@ class TestConfiguration { _id: id, ...existing, roles: roles || {}, + tenantId: TENANT_ID, } await createASession(id, "sessionid") if (builder) { diff --git a/packages/server/src/utilities/global.js b/packages/server/src/utilities/global.js index 3ce794b40..2dbb956d3 100644 --- a/packages/server/src/utilities/global.js +++ b/packages/server/src/utilities/global.js @@ -1,11 +1,9 @@ -const CouchDB = require("../db") const { getMultiIDParams, getGlobalIDFromUserMetadataID, - StaticDatabases, } = require("../db/utils") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") -const { getDeployedAppID } = require("@budibase/auth/db") +const { getDeployedAppID, getGlobalDBFromCtx } = require("@budibase/auth/db") const { getGlobalUserParams } = require("@budibase/auth/db") const { user: userCache } = require("@budibase/auth/cache") @@ -34,18 +32,18 @@ function processUser(appId, user) { } exports.getCachedSelf = async (ctx, appId) => { - const user = await userCache.getUser(ctx.user._id) + const user = await userCache.getUser(ctx.user._id, ctx.user.tenantId) return processUser(appId, user) } -exports.getGlobalUser = async (appId, userId) => { - const db = CouchDB(StaticDatabases.GLOBAL.name) +exports.getGlobalUser = async (ctx, appId, userId) => { + const db = getGlobalDBFromCtx(ctx) let user = await db.get(getGlobalIDFromUserMetadataID(userId)) return processUser(appId, user) } -exports.getGlobalUsers = async (appId = null, users = null) => { - const db = CouchDB(StaticDatabases.GLOBAL.name) +exports.getGlobalUsers = async (ctx, appId = null, users = null) => { + const db = getGlobalDBFromCtx(ctx) let globalUsers if (users) { const globalIds = users.map(user => getGlobalIDFromUserMetadataID(user._id)) diff --git a/packages/server/src/utilities/rowProcessor.js b/packages/server/src/utilities/rowProcessor.js index 766bc09b2..2a83ae5d2 100644 --- a/packages/server/src/utilities/rowProcessor.js +++ b/packages/server/src/utilities/rowProcessor.js @@ -193,13 +193,14 @@ exports.inputProcessing = (user = {}, table, row) => { /** * This function enriches the input rows with anything they are supposed to contain, for example * link records or attachment links. - * @param {string} appId the ID of the application for which rows are being enriched. + * @param {object} ctx the request which is looking for enriched rows. * @param {object} table the table from which these rows came from originally, this is used to determine * the schema of the rows and then enrich. * @param {object[]} rows the rows which are to be enriched. * @returns {object[]} the enriched rows will be returned. */ -exports.outputProcessing = async (appId, table, rows) => { +exports.outputProcessing = async (ctx, table, rows) => { + const appId = ctx.appId let wasArray = true if (!(rows instanceof Array)) { rows = [rows] diff --git a/packages/server/src/utilities/users.js b/packages/server/src/utilities/users.js index 6144397bf..64fbfb7ea 100644 --- a/packages/server/src/utilities/users.js +++ b/packages/server/src/utilities/users.js @@ -3,7 +3,7 @@ const { InternalTables } = require("../db/utils") const { getGlobalUser } = require("../utilities/global") exports.getFullUser = async (ctx, userId) => { - const global = await getGlobalUser(ctx.appId, userId) + const global = await getGlobalUser(ctx, ctx.appId, userId) let metadata try { // this will throw an error if the db doesn't exist, or there is no appId diff --git a/packages/server/src/utilities/workerRequests.js b/packages/server/src/utilities/workerRequests.js index cb06b5b8d..d56111385 100644 --- a/packages/server/src/utilities/workerRequests.js +++ b/packages/server/src/utilities/workerRequests.js @@ -4,11 +4,11 @@ const { checkSlashesInUrl } = require("./index") const { getDeployedAppID } = require("@budibase/auth/db") const { updateAppRole, getGlobalUser } = require("./global") -function request(ctx, request, noApiKey) { +function request(ctx, request) { if (!request.headers) { request.headers = {} } - if (!noApiKey) { + if (!ctx) { request.headers["x-budibase-api-key"] = env.INTERNAL_API_KEY } if (request.body && Object.keys(request.body).length > 0) { @@ -28,12 +28,13 @@ function request(ctx, request, noApiKey) { exports.request = request -exports.sendSmtpEmail = async (to, from, subject, contents) => { +exports.sendSmtpEmail = async (tenantId, to, from, subject, contents) => { const response = await fetch( checkSlashesInUrl(env.WORKER_URL + `/api/admin/email/send`), request(null, { method: "POST", body: { + tenantId, email: to, from, contents, @@ -77,7 +78,7 @@ exports.getGlobalSelf = async (ctx, appId = null) => { const response = await fetch( checkSlashesInUrl(env.WORKER_URL + endpoint), // we don't want to use API key when getting self - request(ctx, { method: "GET" }, true) + request(ctx, { method: "GET" }) ) if (response.status !== 200) { ctx.throw(400, "Unable to get self globally.") @@ -97,7 +98,7 @@ exports.addAppRoleToUser = async (ctx, appId, roleId, userId = null) => { user = await exports.getGlobalSelf(ctx) endpoint = `/api/admin/users/self` } else { - user = await getGlobalUser(appId, userId) + user = await getGlobalUser(ctx, appId, userId) body._id = userId endpoint = `/api/admin/users` } @@ -121,11 +122,11 @@ exports.addAppRoleToUser = async (ctx, appId, roleId, userId = null) => { return response.json() } -exports.removeAppFromUserRoles = async appId => { +exports.removeAppFromUserRoles = async (ctx, appId) => { const deployedAppId = getDeployedAppID(appId) const response = await fetch( checkSlashesInUrl(env.WORKER_URL + `/api/admin/roles/${deployedAppId}`), - request(null, { + request(ctx, { method: "DELETE", }) ) diff --git a/packages/worker/src/api/controllers/admin/auth.js b/packages/worker/src/api/controllers/admin/auth.js index cc86f3232..669ed3cfa 100644 --- a/packages/worker/src/api/controllers/admin/auth.js +++ b/packages/worker/src/api/controllers/admin/auth.js @@ -1,14 +1,12 @@ const authPkg = require("@budibase/auth") const { google } = require("@budibase/auth/src/middleware") const { Configs, EmailTemplatePurpose } = require("../../../constants") -const CouchDB = require("../../../db") const { sendEmail, isEmailConfigured } = require("../../../utilities/email") const { clearCookie, getGlobalUserByEmail, hash } = authPkg.utils const { Cookies } = authPkg.constants const { passport } = authPkg.auth const { checkResetPasswordCode } = require("../../../utilities/redis") - -const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name +const { getGlobalDB } = authPkg.db async function authInternal(ctx, user, err = null) { if (err) { @@ -46,7 +44,8 @@ exports.authenticate = async (ctx, next) => { */ exports.reset = async ctx => { const { email } = ctx.request.body - const configured = await isEmailConfigured() + const tenantId = ctx.params.tenantId + const configured = await isEmailConfigured(tenantId) if (!configured) { ctx.throw( 400, @@ -54,10 +53,10 @@ exports.reset = async ctx => { ) } try { - const user = await getGlobalUserByEmail(email) + const user = await getGlobalUserByEmail(email, tenantId) // only if user exists, don't error though if they don't if (user) { - await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, { + await sendEmail(tenantId, email, EmailTemplatePurpose.PASSWORD_RECOVERY, { user, subject: "{{ company }} platform password reset", }) @@ -77,7 +76,7 @@ exports.resetUpdate = async ctx => { const { resetCode, password } = ctx.request.body try { const userId = await checkResetPasswordCode(resetCode) - const db = new CouchDB(GLOBAL_DB) + const db = new getGlobalDB(ctx.params.tenantId) const user = await db.get(userId) user.password = await hash(password) await db.put(user) @@ -99,7 +98,7 @@ exports.logout = async ctx => { * On a successful login, you will be redirected to the googleAuth callback route. */ exports.googlePreAuth = async (ctx, next) => { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDB(ctx.params.tenantId) const config = await authPkg.db.getScopedConfig(db, { type: Configs.GOOGLE, workspace: ctx.query.workspace, @@ -112,7 +111,7 @@ exports.googlePreAuth = async (ctx, next) => { } exports.googleAuth = async (ctx, next) => { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDB(ctx.params.tenantId) const config = await authPkg.db.getScopedConfig(db, { type: Configs.GOOGLE, diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/admin/configs.js index 55d2df6ee..bf4ded0a2 100644 --- a/packages/worker/src/api/controllers/admin/configs.js +++ b/packages/worker/src/api/controllers/admin/configs.js @@ -1,21 +1,18 @@ const CouchDB = require("../../../db") const { generateConfigID, - StaticDatabases, getConfigParams, getGlobalUserParams, getScopedFullConfig, -} = require("@budibase/auth").db + getGlobalDBFromCtx, + getAllApps, +} = require("@budibase/auth/db") const { Configs } = require("../../../constants") const email = require("../../../utilities/email") const { upload, ObjectStoreBuckets } = require("@budibase/auth").objectStore -const APP_PREFIX = "app_" - -const GLOBAL_DB = StaticDatabases.GLOBAL.name - exports.save = async function (ctx) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) const { type, workspace, user, config } = ctx.request.body // Config does not exist yet @@ -51,7 +48,7 @@ exports.save = async function (ctx) { } exports.fetch = async function (ctx) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) const response = await db.allDocs( getConfigParams( { type: ctx.params.type }, @@ -68,7 +65,7 @@ exports.fetch = async function (ctx) { * The hierarchy is type -> workspace -> user. */ exports.find = async function (ctx) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) const { userId, workspaceId } = ctx.query if (workspaceId && userId) { @@ -101,7 +98,7 @@ exports.find = async function (ctx) { } exports.publicSettings = async function (ctx) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) try { // Find the config with the most granular scope based on context const config = await getScopedFullConfig(db, { @@ -139,7 +136,7 @@ exports.upload = async function (ctx) { // add to configuration structure // TODO: right now this only does a global level - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) let cfgStructure = await getScopedFullConfig(db, { type }) if (!cfgStructure) { cfgStructure = { @@ -159,7 +156,7 @@ exports.upload = async function (ctx) { } exports.destroy = async function (ctx) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) const { id, rev } = ctx.params try { @@ -171,14 +168,13 @@ exports.destroy = async function (ctx) { } exports.configChecklist = async function (ctx) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) try { // TODO: Watch get started video // Apps exist - let allDbs = await CouchDB.allDbs() - const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX)) + const apps = (await getAllApps({ CouchDB })) // They have set up SMTP const smtpConfig = await getScopedFullConfig(db, { @@ -199,7 +195,7 @@ exports.configChecklist = async function (ctx) { const adminUser = users.rows.some(row => row.doc.admin) ctx.body = { - apps: appDbNames.length, + apps: apps.length, smtp: !!smtpConfig, adminUser, oauth: !!oauthConfig, diff --git a/packages/worker/src/api/controllers/admin/email.js b/packages/worker/src/api/controllers/admin/email.js index 4e5719e9c..67b45a110 100644 --- a/packages/worker/src/api/controllers/admin/email.js +++ b/packages/worker/src/api/controllers/admin/email.js @@ -1,18 +1,18 @@ const { sendEmail } = require("../../../utilities/email") -const CouchDB = require("../../../db") -const authPkg = require("@budibase/auth") - -const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name +const { getGlobalDBFromCtx } = require("@budibase/auth/db") exports.sendEmail = async ctx => { - const { workspaceId, email, userId, purpose, contents, from, subject } = + let { tenantId, workspaceId, email, userId, purpose, contents, from, subject } = ctx.request.body let user if (userId) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) user = await db.get(userId) } - const response = await sendEmail(email, purpose, { + if (!tenantId && ctx.user.tenantId) { + tenantId = ctx.user.tenantId + } + const response = await sendEmail(tenantId, email, purpose, { workspaceId, user, contents, diff --git a/packages/worker/src/api/controllers/admin/templates.js b/packages/worker/src/api/controllers/admin/templates.js index dde92ecca..352182c19 100644 --- a/packages/worker/src/api/controllers/admin/templates.js +++ b/packages/worker/src/api/controllers/admin/templates.js @@ -1,5 +1,4 @@ -const { generateTemplateID, StaticDatabases } = require("@budibase/auth").db -const CouchDB = require("../../../db") +const { generateTemplateID, getGlobalDBFromCtx } = require("@budibase/auth/db") const { TemplateMetadata, TemplateBindings, @@ -7,10 +6,8 @@ const { } = require("../../../constants") const { getTemplates } = require("../../../constants/templates") -const GLOBAL_DB = StaticDatabases.GLOBAL.name - exports.save = async ctx => { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) let template = ctx.request.body if (!template.ownerId) { template.ownerId = GLOBAL_OWNER @@ -42,29 +39,29 @@ exports.definitions = async ctx => { } exports.fetch = async ctx => { - ctx.body = await getTemplates() + ctx.body = await getTemplates(ctx) } exports.fetchByType = async ctx => { - ctx.body = await getTemplates({ + ctx.body = await getTemplates(ctx, { type: ctx.params.type, }) } exports.fetchByOwner = async ctx => { - ctx.body = await getTemplates({ + ctx.body = await getTemplates(ctx, { ownerId: ctx.params.ownerId, }) } exports.find = async ctx => { - ctx.body = await getTemplates({ + ctx.body = await getTemplates(ctx, { id: ctx.params.id, }) } exports.destroy = async ctx => { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) await db.remove(ctx.params.id, ctx.params.rev) ctx.message = `Template ${ctx.params.id} deleted.` ctx.status = 200 diff --git a/packages/worker/src/api/controllers/admin/users.js b/packages/worker/src/api/controllers/admin/users.js index f52437926..73c10d007 100644 --- a/packages/worker/src/api/controllers/admin/users.js +++ b/packages/worker/src/api/controllers/admin/users.js @@ -1,17 +1,43 @@ -const CouchDB = require("../../../db") -const { generateGlobalUserID, getGlobalUserParams, StaticDatabases } = - require("@budibase/auth").db -const { hash, getGlobalUserByEmail } = require("@budibase/auth").utils +const { + generateGlobalUserID, + getGlobalUserParams, + getGlobalDB, + getGlobalDBFromCtx, + StaticDatabases +} = require("@budibase/auth/db") +const { hash, getGlobalUserByEmail, newid } = require("@budibase/auth").utils const { UserStatus, EmailTemplatePurpose } = require("../../../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 GLOBAL_DB = StaticDatabases.GLOBAL.name +const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name +const tenantDocId = StaticDatabases.PLATFORM_INFO.docs.tenants + +async function noTenantsExist() { + const db = new CouchDB(PLATFORM_INFO_DB) + const tenants = await db.get(tenantDocId) + return !tenants || !tenants.tenantIds || tenants.tenantIds.length === 0 +} -async function allUsers() { - const db = new CouchDB(GLOBAL_DB) +async function tryAddTenant(tenantId) { + const db = new CouchDB(PLATFORM_INFO_DB) + let tenants = await db.get(tenantDocId) + if (!tenants || !Array.isArray(tenants.tenantIds)) { + tenants = { + tenantIds: [], + } + } + if (tenants.tenantIds.indexOf(tenantId) === -1) { + tenants.tenantIds.push(tenantId) + await db.put(tenants) + } +} + +async function allUsers(ctx) { + const db = getGlobalDBFromCtx(ctx) const response = await db.allDocs( getGlobalUserParams(null, { include_docs: true, @@ -20,16 +46,19 @@ async function allUsers() { return response.rows.map(row => row.doc) } -exports.save = async ctx => { - const db = new CouchDB(GLOBAL_DB) - const { email, password, _id } = ctx.request.body - +async function saveUser(user, tenantId) { + if (!tenantId) { + throw "No tenancy specified." + } + const db = getGlobalDB(tenantId) + await tryAddTenant(tenantId) + const { email, password, _id } = user // make sure another user isn't using the same email let dbUser if (email) { dbUser = await getGlobalUserByEmail(email) if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) { - ctx.throw(400, "Email address already in use.") + throw "Email address already in use." } } else { dbUser = await db.get(_id) @@ -42,14 +71,15 @@ exports.save = async ctx => { } else if (dbUser) { hashedPassword = dbUser.password } else { - ctx.throw(400, "Password must be specified.") + throw "Password must be specified." } - let user = { + user = { ...dbUser, - ...ctx.request.body, + ...user, _id: _id || generateGlobalUserID(), password: hashedPassword, + tenantId, } // make sure the roles object is always present if (!user.roles) { @@ -65,34 +95,37 @@ exports.save = async ctx => { ...user, }) await userCache.invalidateUser(response.id) - ctx.body = { + return { _id: response.id, _rev: response.rev, email, } } catch (err) { if (err.status === 409) { - ctx.throw(400, "User exists already") + throw "User exists already" } else { - ctx.throw(err.status, err) + throw err } } } -exports.adminUser = async ctx => { - const db = new CouchDB(GLOBAL_DB) - const response = await db.allDocs( - getGlobalUserParams(null, { - include_docs: true, - }) - ) +exports.save = async ctx => { + // this always stores the user into the requesting users tenancy + const tenantId = ctx.user.tenantId + try { + ctx.body = await saveUser(ctx.request.body, tenantId) + } catch (err) { + ctx.throw(err.status || 400, err) + } +} - if (response.rows.some(row => row.doc.admin)) { +exports.adminUser = async ctx => { + if (!await noTenantsExist()) { ctx.throw(403, "You cannot initialise once an admin user has been created.") } const { email, password } = ctx.request.body - ctx.request.body = { + const user = { email: email, password: password, roles: {}, @@ -103,11 +136,15 @@ exports.adminUser = async ctx => { global: true, }, } - await exports.save(ctx) + try { + ctx.body = await saveUser(user, newid()) + } catch (err) { + ctx.throw(err.status || 400, err) + } } exports.destroy = async ctx => { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) const dbUser = await db.get(ctx.params.id) await db.remove(dbUser._id, dbUser._rev) await userCache.invalidateUser(dbUser._id) @@ -119,7 +156,7 @@ exports.destroy = async ctx => { exports.removeAppRole = async ctx => { const { appId } = ctx.params - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) const users = await allUsers() const bulk = [] const cacheInvalidations = [] @@ -149,7 +186,7 @@ exports.getSelf = async ctx => { } exports.updateSelf = async ctx => { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) const user = await db.get(ctx.user._id) if (ctx.request.body.password) { ctx.request.body.password = await hash(ctx.request.body.password) @@ -170,7 +207,7 @@ exports.updateSelf = async ctx => { // called internally by app server user fetch exports.fetch = async ctx => { - const users = await allUsers() + const users = await allUsers(ctx) // user hashed password shouldn't ever be returned for (let user of users) { if (user) { @@ -182,7 +219,7 @@ exports.fetch = async ctx => { // called internally by app server user find exports.find = async ctx => { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) let user try { user = await db.get(ctx.params.id) @@ -198,11 +235,12 @@ exports.find = async ctx => { exports.invite = async ctx => { const { email, userInfo } = ctx.request.body - const existing = await getGlobalUserByEmail(email) + const tenantId = ctx.user.tenantId + const existing = await getGlobalUserByEmail(email, tenantId) if (existing) { ctx.throw(400, "Email address already in use.") } - await sendEmail(email, EmailTemplatePurpose.INVITATION, { + await sendEmail(tenantId, email, EmailTemplatePurpose.INVITATION, { subject: "{{ company }} platform invitation", info: userInfo, }) diff --git a/packages/worker/src/api/controllers/admin/workspaces.js b/packages/worker/src/api/controllers/admin/workspaces.js index e99155ffb..233f34576 100644 --- a/packages/worker/src/api/controllers/admin/workspaces.js +++ b/packages/worker/src/api/controllers/admin/workspaces.js @@ -1,11 +1,8 @@ -const CouchDB = require("../../../db") -const { getWorkspaceParams, generateWorkspaceID, StaticDatabases } = - require("@budibase/auth").db - -const GLOBAL_DB = StaticDatabases.GLOBAL.name +const { getWorkspaceParams, generateWorkspaceID, getGlobalDBFromCtx } = + require("@budibase/auth/db") exports.save = async function (ctx) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) const workspaceDoc = ctx.request.body // workspace does not exist yet @@ -25,7 +22,7 @@ exports.save = async function (ctx) { } exports.fetch = async function (ctx) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) const response = await db.allDocs( getWorkspaceParams(undefined, { include_docs: true, @@ -35,7 +32,7 @@ exports.fetch = async function (ctx) { } exports.find = async function (ctx) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) try { ctx.body = await db.get(ctx.params.id) } catch (err) { @@ -44,7 +41,7 @@ exports.find = async function (ctx) { } exports.destroy = async function (ctx) { - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDBFromCtx(ctx) const { id, rev } = ctx.params try { diff --git a/packages/worker/src/api/routes/admin/auth.js b/packages/worker/src/api/routes/admin/auth.js index 04e30fc00..c26acec6c 100644 --- a/packages/worker/src/api/routes/admin/auth.js +++ b/packages/worker/src/api/routes/admin/auth.js @@ -30,14 +30,14 @@ function buildResetUpdateValidation() { router .post("/api/admin/auth", buildAuthValidation(), authController.authenticate) - .post("/api/admin/auth/reset", buildResetValidation(), authController.reset) + .post("/api/admin/auth/:tenantId/reset", buildResetValidation(), authController.reset) .post( - "/api/admin/auth/reset/update", + "/api/admin/auth/:tenantId/reset/update", buildResetUpdateValidation(), authController.resetUpdate ) .post("/api/admin/auth/logout", authController.logout) - .get("/api/admin/auth/google", authController.googlePreAuth) - .get("/api/admin/auth/google/callback", authController.googleAuth) + .get("/api/admin/auth/:tenantId/google", authController.googlePreAuth) + .get("/api/admin/auth/:tenantId/google/callback", authController.googleAuth) module.exports = router diff --git a/packages/worker/src/constants/templates/index.js b/packages/worker/src/constants/templates/index.js index c677f504c..026ebf6b9 100644 --- a/packages/worker/src/constants/templates/index.js +++ b/packages/worker/src/constants/templates/index.js @@ -6,8 +6,7 @@ const { GLOBAL_OWNER, } = require("../index") const { join } = require("path") -const CouchDB = require("../../db") -const { getTemplateParams, StaticDatabases } = require("@budibase/auth").db +const { getTemplateParams, getGlobalDBFromCtx } = require("@budibase/auth/db") exports.EmailTemplates = { [EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile( @@ -49,8 +48,8 @@ exports.addBaseTemplates = (templates, type = null) => { return templates } -exports.getTemplates = async ({ ownerId, type, id } = {}) => { - const db = new CouchDB(StaticDatabases.GLOBAL.name) +exports.getTemplates = async (ctx, { ownerId, type, id } = {}) => { + const db = getGlobalDBFromCtx(ctx) const response = await db.allDocs( getTemplateParams(ownerId || GLOBAL_OWNER, id, { include_docs: true, @@ -67,7 +66,7 @@ exports.getTemplates = async ({ ownerId, type, id } = {}) => { return exports.addBaseTemplates(templates, type) } -exports.getTemplateByPurpose = async (type, purpose) => { - const templates = await exports.getTemplates({ type }) +exports.getTemplateByPurpose = async (ctx, type, purpose) => { + const templates = await exports.getTemplates(ctx, { type }) return templates.find(template => template.purpose === purpose) } diff --git a/packages/worker/src/utilities/email.js b/packages/worker/src/utilities/email.js index d0441d552..38fafd101 100644 --- a/packages/worker/src/utilities/email.js +++ b/packages/worker/src/utilities/email.js @@ -1,6 +1,5 @@ const nodemailer = require("nodemailer") -const CouchDB = require("../db") -const { StaticDatabases, getScopedConfig } = require("@budibase/auth").db +const { getGlobalDB, getScopedConfig } = require("@budibase/auth/db") const { EmailTemplatePurpose, TemplateTypes, Configs } = require("../constants") const { getTemplateByPurpose } = require("../constants/templates") const { getSettingsTemplateContext } = require("./templates") @@ -8,7 +7,6 @@ const { processString } = require("@budibase/string-templates") const { getResetPasswordCode, getInviteCode } = require("../utilities/redis") const TEST_MODE = false -const GLOBAL_DB = StaticDatabases.GLOBAL.name const TYPE = TemplateTypes.EMAIL const FULL_EMAIL_PURPOSES = [ @@ -116,15 +114,14 @@ async function getSmtpConfiguration(db, workspaceId = null) { /** * Checks if a SMTP config exists based on passed in parameters. - * @param workspaceId * @return {Promise} returns true if there is a configuration that can be used. */ -exports.isEmailConfigured = async (workspaceId = null) => { +exports.isEmailConfigured = async (tenantId, workspaceId = null) => { // when "testing" simply return true if (TEST_MODE) { return true } - const db = new CouchDB(GLOBAL_DB) + const db = getGlobalDB(tenantId) const config = await getSmtpConfiguration(db, workspaceId) return config != null } @@ -132,6 +129,7 @@ exports.isEmailConfigured = async (workspaceId = null) => { /** * Given an email address and an email purpose this will retrieve the SMTP configuration and * send an email using it. + * @param {string} tenantId The tenant which is sending them email. * @param {string} email The email address to send to. * @param {string} purpose The purpose of the email being sent (e.g. reset password). * @param {string|undefined} workspaceId If finer grain controls being used then this will lookup config for workspace. @@ -144,11 +142,12 @@ exports.isEmailConfigured = async (workspaceId = null) => { * nodemailer response. */ exports.sendEmail = async ( + tenantId, email, purpose, { workspaceId, user, from, contents, subject, info } = {} ) => { - const db = new CouchDB(GLOBAL_DB) + const db = new getGlobalDB(tenantId) let config = (await getSmtpConfiguration(db, workspaceId)) || {} if (Object.keys(config).length === 0 && !TEST_MODE) { throw "Unable to find SMTP configuration." @@ -156,7 +155,7 @@ exports.sendEmail = async ( const transport = createSMTPTransport(config) // if there is a link code needed this will retrieve it const code = await getLinkCode(purpose, email, user, info) - const context = await getSettingsTemplateContext(purpose, code) + const context = await getSettingsTemplateContext(tenantId, purpose, code) const message = { from: from || config.from, to: email, diff --git a/packages/worker/src/utilities/templates.js b/packages/worker/src/utilities/templates.js index 3ac897c10..dfd139fb8 100644 --- a/packages/worker/src/utilities/templates.js +++ b/packages/worker/src/utilities/templates.js @@ -1,5 +1,4 @@ -const CouchDB = require("../db") -const { getScopedConfig, StaticDatabases } = require("@budibase/auth").db +const { getScopedConfig, getGlobalDB } = require("@budibase/auth/db") const { Configs, InternalTemplateBindings, @@ -12,8 +11,8 @@ const env = require("../environment") const LOCAL_URL = `http://localhost:${env.CLUSTER_PORT || 10000}` const BASE_COMPANY = "Budibase" -exports.getSettingsTemplateContext = async (purpose, code = null) => { - const db = new CouchDB(StaticDatabases.GLOBAL.name) +exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => { + const db = new getGlobalDB(tenantId) // TODO: use more granular settings in the future if required let settings = (await getScopedConfig(db, { type: Configs.SETTINGS })) || {} if (!settings || !settings.platformUrl) { From f0f9736c0b91d290841e248f0ec4eb7383b0d24c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 16 Jul 2021 15:08:58 +0100 Subject: [PATCH 008/173] Updating auth to utilise the tenant system. --- packages/auth/src/cache/user.js | 2 +- packages/auth/src/db/utils.js | 12 +++++++----- packages/auth/src/index.js | 2 +- .../auth/src/middleware/passport/google.js | 2 +- .../auth/src/middleware/passport/local.js | 19 +++++++++++++------ packages/auth/src/utils.js | 12 ++++-------- .../auth/_components/GoogleButton.svelte | 5 +++-- packages/builder/src/stores/portal/auth.js | 13 ++++++++++--- .../builder/src/stores/portal/organisation.js | 2 +- .../src/tests/utilities/TestConfiguration.js | 2 +- .../src/api/controllers/admin/configs.js | 4 +++- .../worker/src/api/controllers/admin/users.js | 2 +- packages/worker/src/api/controllers/app.js | 11 ++--------- packages/worker/src/api/index.js | 14 +++++++------- packages/worker/src/api/routes/admin/auth.js | 2 +- .../tests/utilities/TestConfiguration.js | 5 ++++- 16 files changed, 60 insertions(+), 49 deletions(-) diff --git a/packages/auth/src/cache/user.js b/packages/auth/src/cache/user.js index b49721a54..616612a58 100644 --- a/packages/auth/src/cache/user.js +++ b/packages/auth/src/cache/user.js @@ -6,7 +6,7 @@ const EXPIRY_SECONDS = 3600 exports.getUser = async (userId, tenantId = null) => { if (!tenantId) { - tenantId = await lookupTenantId({ userId }) + tenantId = await lookupTenantId(userId) } const client = await redis.getUserClient() // try cache diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index f305d18d0..1a69bb551 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -4,6 +4,7 @@ const { getDB } = require("./index") const UNICODE_MAX = "\ufff0" const SEPARATOR = "_" +const DEFAULT_TENANT = "default" exports.ViewNames = { USER_BY_EMAIL: "by_email", @@ -72,19 +73,20 @@ function getDocParams(docType, docId = null, otherProps = {}) { * Gets the name of the global DB to connect to in a multi-tenancy system. */ exports.getGlobalDB = tenantId => { - const globalName = exports.StaticDatabases.GLOBAL.name // fallback for system pre multi-tenancy - if (!tenantId) { - return globalName + let dbName = exports.StaticDatabases.GLOBAL.name + if (tenantId && tenantId !== DEFAULT_TENANT) { + dbName = `${tenantId}${SEPARATOR}${dbName}` } - return getDB(`${tenantId}${SEPARATOR}${globalName}`) + return getDB(dbName) } /** * Given a koa context this tries to find the correct tenant Global DB. */ exports.getGlobalDBFromCtx = ctx => { - return exports.getGlobalDB(ctx.user.tenantId) + const user = ctx.user || {} + return exports.getGlobalDB(user.tenantId) } /** diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index 2e398d8c5..e8eeb6c8d 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -1,7 +1,7 @@ const passport = require("koa-passport") const LocalStrategy = require("passport-local").Strategy const JwtStrategy = require("passport-jwt").Strategy -const { getGlobalDB } = require("./db/utils") +const { getGlobalDB, StaticDatabases } = require("./db/utils") const { jwt, local, authenticated, google, auditLog } = require("./middleware") const { setDB } = require("./db") const userCache = require("./cache/user") diff --git a/packages/auth/src/middleware/passport/google.js b/packages/auth/src/middleware/passport/google.js index bc6812157..446e9b5cb 100644 --- a/packages/auth/src/middleware/passport/google.js +++ b/packages/auth/src/middleware/passport/google.js @@ -13,7 +13,7 @@ const { lookupTenantId } = require("../../utils") async function authenticate(token, tokenSecret, profile, done) { // Check the user exists in the instance DB by email const userId = generateGlobalUserID(profile.id) - const tenantId = await lookupTenantId({ userId }) + const tenantId = await lookupTenantId(userId) const db = getGlobalDB(tenantId) let dbUser diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js index 147305e31..9ed837e1f 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/auth/src/middleware/passport/local.js @@ -8,20 +8,27 @@ const { createASession } = require("../../security/sessions") const INVALID_ERR = "Invalid Credentials" -exports.options = {} +exports.options = { + passReqToCallback: true, +} /** * Passport Local Authentication Middleware. - * @param {*} email - username to login with - * @param {*} password - plain text password to log in with - * @param {*} done - callback from passport to return user information and errors + * @param {*} ctx the request structure + * @param {*} email username to login with + * @param {*} password plain text password to log in with + * @param {*} done callback from passport to return user information and errors * @returns The authenticated user, or errors if they occur */ -exports.authenticate = async function (email, password, done) { +exports.authenticate = async function (ctx, email, password, done) { if (!email) return done(null, false, "Email Required.") if (!password) return done(null, false, "Password Required.") + const params = ctx.params || {} + const query = ctx.query || {} - const dbUser = await getGlobalUserByEmail(email) + // use the request to find the tenantId + const tenantId = params.tenantId || query.tenantId + const dbUser = await getGlobalUserByEmail(email, tenantId) if (dbUser == null) { return done(null, false, { message: "User not found" }) } diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index b5225881d..fe9230ea2 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -101,10 +101,9 @@ exports.isClient = ctx => { return ctx.headers["x-budibase-type"] === "client" } -exports.lookupTenantId = async ({ email, userId }) => { - const toQuery = email || userId +exports.lookupTenantId = async userId => { const db = getDB(StaticDatabases.PLATFORM_INFO.name) - const doc = await db.get(toQuery) + const doc = await db.get(userId) if (!doc || !doc.tenantId) { throw "Unable to find tenant" } @@ -118,13 +117,10 @@ exports.lookupTenantId = async ({ email, userId }) => { * @param {string|null} tenantId If tenant ID is known it can be specified * @return {Promise} */ -exports.getGlobalUserByEmail = async (email, tenantId = null) => { +exports.getGlobalUserByEmail = async (email, tenantId) => { if (email == null) { throw "Must supply an email address to view" } - if (!tenantId) { - tenantId = await exports.lookupTenantId({ email }) - } const db = getGlobalDB(tenantId) try { let users = ( @@ -138,7 +134,7 @@ exports.getGlobalUserByEmail = async (email, tenantId = null) => { } catch (err) { if (err != null && err.name === "not_found") { await createUserEmailView(db) - return exports.getGlobalUserByEmail(email) + return exports.getGlobalUserByEmail(email, tenantId) } else { throw err } diff --git a/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte b/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte index ffd870c21..b5d3394fd 100644 --- a/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte +++ b/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte @@ -1,16 +1,17 @@ {#if show} window.open("/api/admin/auth/google", "_blank")} + on:click={() => window.open(`/api/admin/auth/${tenantId}/google`, "_blank")} >
google icon diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index ef91c114f..e34fd2584 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -7,6 +7,7 @@ export function createAuthStore() { let initials = null let isAdmin = false let isBuilder = false + let tenantId = "default" if ($user) { if ($user.firstName) { initials = $user.firstName[0] @@ -20,18 +21,21 @@ export function createAuthStore() { } isAdmin = !!$user.admin?.global isBuilder = !!$user.builder?.global + tenantId = $user.tenantId || "default" } return { user: $user, initials, isAdmin, isBuilder, + tenantId, } }) return { subscribe: store.subscribe, checkAuth: async () => { + const response = await api.get("/api/admin/users/self") if (response.status !== 200) { user.set(null) @@ -41,7 +45,8 @@ export function createAuthStore() { } }, login: async creds => { - const response = await api.post(`/api/admin/auth`, creds) + const tenantId = get(store).tenantId + const response = await api.post(`/api/admin/auth/${tenantId}/login`, creds) const json = await response.json() if (response.status === 200) { user.set(json.user) @@ -68,7 +73,8 @@ export function createAuthStore() { } }, forgotPassword: async email => { - const response = await api.post(`/api/admin/auth/reset`, { + const tenantId = get(store).tenantId + const response = await api.post(`/api/admin/auth/${tenantId}/reset`, { email, }) if (response.status !== 200) { @@ -77,7 +83,8 @@ export function createAuthStore() { await response.json() }, resetPassword: async (password, code) => { - const response = await api.post(`/api/admin/auth/reset/update`, { + const tenantId = get(store).tenantId + const response = await api.post(`/api/admin/auth/${tenantId}/reset/update`, { password, resetCode: code, }) diff --git a/packages/builder/src/stores/portal/organisation.js b/packages/builder/src/stores/portal/organisation.js index 7e6a777cd..6c0d5412a 100644 --- a/packages/builder/src/stores/portal/organisation.js +++ b/packages/builder/src/stores/portal/organisation.js @@ -2,7 +2,7 @@ import { writable, get } from "svelte/store" import api from "builderStore/api" const DEFAULT_CONFIG = { - platformUrl: "http://localhost:1000", + platformUrl: "http://localhost:10000", logoUrl: undefined, docsUrl: undefined, company: "Budibase", diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index 3a883b4a7..ceef2a3e9 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -23,7 +23,7 @@ const { user: userCache } = require("@budibase/auth/cache") const GLOBAL_USER_ID = "us_uuid1" const EMAIL = "babs@babs.com" const PASSWORD = "babs_password" -const TENANT_ID = "tenant1" +const TENANT_ID = "default" class TestConfiguration { constructor(openServer = true) { diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/admin/configs.js index bf4ded0a2..02dd18360 100644 --- a/packages/worker/src/api/controllers/admin/configs.js +++ b/packages/worker/src/api/controllers/admin/configs.js @@ -5,6 +5,7 @@ const { getGlobalUserParams, getScopedFullConfig, getGlobalDBFromCtx, + getGlobalDB, getAllApps, } = require("@budibase/auth/db") const { Configs } = require("../../../constants") @@ -168,7 +169,8 @@ exports.destroy = async function (ctx) { } exports.configChecklist = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const tenantId = ctx.query.tenantId + const db = tenantId ? getGlobalDB(tenantId) : getGlobalDBFromCtx(ctx) try { // TODO: Watch get started video diff --git a/packages/worker/src/api/controllers/admin/users.js b/packages/worker/src/api/controllers/admin/users.js index 73c10d007..a27b42d59 100644 --- a/packages/worker/src/api/controllers/admin/users.js +++ b/packages/worker/src/api/controllers/admin/users.js @@ -56,7 +56,7 @@ async function saveUser(user, tenantId) { // make sure another user isn't using the same email let dbUser if (email) { - dbUser = await getGlobalUserByEmail(email) + dbUser = await getGlobalUserByEmail(email, tenantId) if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) { throw "Email address already in use." } diff --git a/packages/worker/src/api/controllers/app.js b/packages/worker/src/api/controllers/app.js index ff9692a5e..782c45bc1 100644 --- a/packages/worker/src/api/controllers/app.js +++ b/packages/worker/src/api/controllers/app.js @@ -1,18 +1,11 @@ -const { DocumentTypes } = require("@budibase/auth").db +const { getAllApps } = require("@budibase/auth/db") const CouchDB = require("../../db") -const APP_PREFIX = "app_" const URL_REGEX_SLASH = /\/|\\/g exports.getApps = async ctx => { - // allDbs call of CouchDB is very inaccurate in production - const allDbs = await CouchDB.allDbs() - const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX)) - const appPromises = appDbNames.map(db => - new CouchDB(db).get(DocumentTypes.APP_METADATA) - ) + const apps = await getAllApps({ CouchDB }) - const apps = await Promise.allSettled(appPromises) const body = {} for (let app of apps) { if (app.status !== "fulfilled") { diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js index bda57863f..a62cd4db5 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -14,27 +14,27 @@ const PUBLIC_ENDPOINTS = [ method: "POST", }, { - route: "/api/admin/auth", + route: "/api/admin/auth/:tenantId/login", method: "POST", }, { - route: "/api/admin/auth/google", + route: "/api/admin/auth/:tenantId/google", method: "GET", }, { - route: "/api/admin/auth/google/callback", + route: "/api/admin/auth/:tenantId/google/callback", method: "GET", }, { - route: "/api/admin/auth/reset", + route: "/api/admin/auth/:tenantId/reset", method: "POST", }, { - route: "/api/admin/configs/checklist", - method: "GET", + route: "/api/admin/auth/:tenantId/reset/update", + method: "POST", }, { - route: "/api/apps", + route: "/api/admin/configs/checklist", method: "GET", }, { diff --git a/packages/worker/src/api/routes/admin/auth.js b/packages/worker/src/api/routes/admin/auth.js index c26acec6c..3fef6d723 100644 --- a/packages/worker/src/api/routes/admin/auth.js +++ b/packages/worker/src/api/routes/admin/auth.js @@ -29,7 +29,7 @@ function buildResetUpdateValidation() { } router - .post("/api/admin/auth", buildAuthValidation(), authController.authenticate) + .post("/api/admin/auth/:tenantId/login", buildAuthValidation(), authController.authenticate) .post("/api/admin/auth/:tenantId/reset", buildResetValidation(), authController.reset) .post( "/api/admin/auth/:tenantId/reset/update", diff --git a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js index c205a45e3..593c0edd5 100644 --- a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js @@ -7,6 +7,8 @@ const { Configs, LOGO_URL } = require("../../../../constants") const { getGlobalUserByEmail } = require("@budibase/auth").utils const { createASession } = require("@budibase/auth/sessions") +const TENANT_ID = "default" + class TestConfiguration { constructor(openServer = true) { if (openServer) { @@ -72,6 +74,7 @@ class TestConfiguration { _id: "us_uuid1", userId: "us_uuid1", sessionId: "sessionid", + tenantId: TENANT_ID, } const authToken = jwt.sign(user, env.JWT_SECRET) return { @@ -81,7 +84,7 @@ class TestConfiguration { } async getUser(email) { - return getGlobalUserByEmail(email) + return getGlobalUserByEmail(email, TENANT_ID) } async createUser(email = "test@test.com", password = "test") { From 037dce5016130a87a022c611fe9c6d6c796eb1f6 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 16 Jul 2021 18:04:49 +0100 Subject: [PATCH 009/173] Further work, tenancy now working but some more work to be done. --- packages/auth/src/constants.js | 2 + packages/auth/src/db/utils.js | 19 +++- .../auth/src/middleware/passport/local.js | 6 +- .../src/pages/builder/admin/index.svelte | 1 + .../src/pages/builder/auth/login.svelte | 3 + .../builder/src/pages/builder/auth/org.svelte | 95 +++++++++++++++++++ packages/builder/src/stores/portal/admin.js | 6 +- packages/builder/src/stores/portal/auth.js | 22 +++-- .../server/src/api/controllers/apikeys.js | 2 - .../server/src/api/controllers/application.js | 7 +- .../server/src/api/controllers/automation.js | 2 +- packages/server/src/utilities/index.js | 2 - .../src/api/controllers/admin/configs.js | 4 +- .../worker/src/api/controllers/admin/email.js | 12 ++- .../worker/src/api/controllers/admin/roles.js | 3 +- .../worker/src/api/controllers/admin/users.js | 30 +++--- .../src/api/controllers/admin/workspaces.js | 7 +- packages/worker/src/api/controllers/app.js | 4 +- packages/worker/src/api/routes/admin/auth.js | 12 ++- packages/worker/src/api/routes/admin/users.js | 1 + 20 files changed, 192 insertions(+), 48 deletions(-) create mode 100644 packages/builder/src/pages/builder/auth/org.svelte diff --git a/packages/auth/src/constants.js b/packages/auth/src/constants.js index f96bea547..a06585744 100644 --- a/packages/auth/src/constants.js +++ b/packages/auth/src/constants.js @@ -21,3 +21,5 @@ exports.Configs = { SMTP: "smtp", GOOGLE: "google", } + +exports.DEFAULT_TENANT_ID = "default" diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 1a69bb551..fd2395c8f 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -1,10 +1,10 @@ const { newid } = require("../hashing") const Replication = require("./Replication") -const { getDB } = require("./index") +const { getDB, getCouch } = require("./index") +const { DEFAULT_TENANT_ID } = require("../constants") const UNICODE_MAX = "\ufff0" const SEPARATOR = "_" -const DEFAULT_TENANT = "default" exports.ViewNames = { USER_BY_EMAIL: "by_email", @@ -75,7 +75,7 @@ function getDocParams(docType, docId = null, otherProps = {}) { exports.getGlobalDB = tenantId => { // fallback for system pre multi-tenancy let dbName = exports.StaticDatabases.GLOBAL.name - if (tenantId && tenantId !== DEFAULT_TENANT) { + if (tenantId && tenantId !== DEFAULT_TENANT_ID) { dbName = `${tenantId}${SEPARATOR}${dbName}` } return getDB(dbName) @@ -192,7 +192,11 @@ exports.getDeployedAppID = appId => { * different users/companies apps as there is no security around it - all apps are returned. * @return {Promise} returns the app information document stored in each app database. */ -exports.getAllApps = async ({ CouchDB, dev, all } = {}) => { +exports.getAllApps = async ({ tenantId, dev, all } = {}) => { + if (!tenantId) { + tenantId = DEFAULT_TENANT_ID + } + const CouchDB = getCouch() let allDbs = await CouchDB.allDbs() const appDbNames = allDbs.filter(dbName => dbName.startsWith(exports.APP_PREFIX) @@ -206,10 +210,15 @@ exports.getAllApps = async ({ CouchDB, dev, all } = {}) => { } else { const response = await Promise.allSettled(appPromises) const apps = response - .filter(result => result.status === "fulfilled") + .filter(result => result.status === "fulfilled" ) .map(({ value }) => value) + .filter(app => { + const appTenant = !app.tenantId ? DEFAULT_TENANT_ID : app.tenantId + return tenantId === appTenant + }) if (!all) { return apps.filter(app => { + if (dev) { return isDevApp(app) } diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js index 9ed837e1f..802e31d1a 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/auth/src/middleware/passport/local.js @@ -1,5 +1,5 @@ const jwt = require("jsonwebtoken") -const { UserStatus } = require("../../constants") +const { UserStatus, DEFAULT_TENANT_ID } = require("../../constants") const { compare } = require("../../hashing") const env = require("../../environment") const { getGlobalUserByEmail } = require("../../utils") @@ -24,10 +24,9 @@ exports.authenticate = async function (ctx, email, password, done) { if (!email) return done(null, false, "Email Required.") if (!password) return done(null, false, "Password Required.") const params = ctx.params || {} - const query = ctx.query || {} // use the request to find the tenantId - const tenantId = params.tenantId || query.tenantId + let tenantId = params.tenantId || DEFAULT_TENANT_ID const dbUser = await getGlobalUserByEmail(email, tenantId) if (dbUser == null) { return done(null, false, { message: "User not found" }) @@ -41,7 +40,6 @@ exports.authenticate = async function (ctx, email, password, done) { // authenticate if (await compare(password, dbUser.password)) { const sessionId = newid() - const tenantId = dbUser.tenantId await createASession(dbUser._id, { sessionId, tenantId }) dbUser.token = jwt.sign( diff --git a/packages/builder/src/pages/builder/admin/index.svelte b/packages/builder/src/pages/builder/admin/index.svelte index f8cbc2145..4a6ded819 100644 --- a/packages/builder/src/pages/builder/admin/index.svelte +++ b/packages/builder/src/pages/builder/admin/index.svelte @@ -44,6 +44,7 @@ + diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte index 9fb984c73..88a87e173 100644 --- a/packages/builder/src/pages/builder/auth/login.svelte +++ b/packages/builder/src/pages/builder/auth/login.svelte @@ -15,6 +15,7 @@ import Logo from "assets/bb-emblem.svg" import { onMount } from "svelte" + let tenantId = "" let username = "" let password = "" @@ -25,6 +26,7 @@ await auth.login({ username, password, + tenantId, }) notifications.success("Logged in successfully") if ($auth?.user?.forceResetPassword) { @@ -64,6 +66,7 @@ Sign in with email + + import { + ActionButton, + Body, + Button, + Divider, + Heading, + Input, + Layout, + notifications, + } from "@budibase/bbui" + import { goto, params } from "@roxi/routify" + import { auth, organisation } from "stores/portal" + import GoogleButton from "./_components/GoogleButton.svelte" + import Logo from "assets/bb-emblem.svg" + import { onMount } from "svelte" + + let tenantId = "" + let username = "" + let password = "" + + $: company = $organisation.company || "Budibase" + + async function login() { + try { + await auth.login({ + username, + password, + tenantId, + }) + notifications.success("Logged in successfully") + if ($auth?.user?.forceResetPassword) { + $goto("./reset") + } else { + if ($params["?returnUrl"]) { + window.location = decodeURIComponent($params["?returnUrl"]) + } else { + notifications.success("Logged in successfully") + $goto("../portal") + } + } + } catch (err) { + console.error(err) + notifications.error("Invalid credentials") + } + } + + function handleKeydown(evt) { + if (evt.key === "Enter") login() + } + + onMount(async () => { + await organisation.init() + }) + + + + + + diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index 33eb23a64..dab56a922 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -1,12 +1,14 @@ -import { writable } from "svelte/store" +import { writable, get } from "svelte/store" import api from "builderStore/api" +import { auth } from "stores/portal" export function createAdminStore() { const { subscribe, set } = writable({}) async function init() { try { - const response = await api.get("/api/admin/configs/checklist") + const tenantId = get(auth).tenantId + const response = await api.get(`/api/admin/configs/checklist?tenantId=${tenantId}`) const json = await response.json() const onboardingSteps = Object.keys(json) diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index e34fd2584..56762532d 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -21,7 +21,7 @@ export function createAuthStore() { } isAdmin = !!$user.admin?.global isBuilder = !!$user.builder?.global - tenantId = $user.tenantId || "default" + tenantId = $user.tenantId || tenantId } return { user: $user, @@ -35,7 +35,6 @@ export function createAuthStore() { return { subscribe: store.subscribe, checkAuth: async () => { - const response = await api.get("/api/admin/users/self") if (response.status !== 200) { user.set(null) @@ -45,8 +44,12 @@ export function createAuthStore() { } }, login: async creds => { - const tenantId = get(store).tenantId - const response = await api.post(`/api/admin/auth/${tenantId}/login`, creds) + const tenantId = creds.tenantId || get(store).tenantId + delete creds.tenantId + const response = await api.post( + `/api/admin/auth/${tenantId}/login`, + creds + ) const json = await response.json() if (response.status === 200) { user.set(json.user) @@ -84,10 +87,13 @@ export function createAuthStore() { }, resetPassword: async (password, code) => { const tenantId = get(store).tenantId - const response = await api.post(`/api/admin/auth/${tenantId}/reset/update`, { - password, - resetCode: code, - }) + const response = await api.post( + `/api/admin/auth/${tenantId}/reset/update`, + { + password, + resetCode: code, + } + ) if (response.status !== 200) { throw "Unable to reset password" } diff --git a/packages/server/src/api/controllers/apikeys.js b/packages/server/src/api/controllers/apikeys.js index 98dee4699..714478894 100644 --- a/packages/server/src/api/controllers/apikeys.js +++ b/packages/server/src/api/controllers/apikeys.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../db") const { StaticDatabases, getGlobalDBFromCtx } = require("@budibase/auth/db") const KEYS_DOC = StaticDatabases.GLOBAL.docs.apiKeys @@ -22,7 +21,6 @@ async function setBuilderMainDoc(ctx, doc) { return db.put(doc) } - exports.fetch = async function (ctx) { try { const mainDoc = await getBuilderMainDoc(ctx) diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index c01d43c86..e91ac08d6 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -25,7 +25,7 @@ const { BASE_LAYOUTS } = require("../../constants/layouts") const { createHomeScreen } = require("../../constants/screens") const { cloneDeep } = require("lodash/fp") const { processObject } = require("@budibase/string-templates") -const { getAllApps } = require("../../utilities") +const { getAllApps } = require("@budibase/auth/db") const { USERS_TABLE_SCHEMA } = require("../../constants") const { getDeployedApps, @@ -128,7 +128,8 @@ async function createInstance(template) { exports.fetch = async function (ctx) { const dev = ctx.query && ctx.query.status === AppStatus.DEV const all = ctx.query && ctx.query.status === AppStatus.ALL - const apps = await getAllApps({ CouchDB, dev, all }) + const tenantId = ctx.user.tenantId + const apps = await getAllApps({ tenantId, dev, all }) // get the locks for all the dev apps if (dev || all) { @@ -188,6 +189,7 @@ exports.fetchAppPackage = async function (ctx) { } exports.create = async function (ctx) { + const tenantId = ctx.user.tenantId const { useTemplate, templateKey } = ctx.request.body const instanceConfig = { useTemplate, @@ -220,6 +222,7 @@ exports.create = async function (ctx) { url: url, template: ctx.request.body.template, instance: instance, + tenantId, updatedAt: new Date().toISOString(), createdAt: new Date().toISOString(), } diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index c54a6803f..f61907687 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -151,6 +151,7 @@ exports.create = async function (ctx) { const db = new CouchDB(ctx.appId) let automation = ctx.request.body automation.appId = ctx.appId + automation.tenantId = ctx.user.tenantId // call through to update if already exists if (automation._id && automation._rev) { @@ -159,7 +160,6 @@ exports.create = async function (ctx) { automation._id = generateAutomationID() - automation.tenantId = ctx.user.tenantId automation.type = "automation" automation = cleanAutomationInputs(automation) automation = await checkForWebhooks({ diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index 320d4a3eb..182ad5182 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -1,6 +1,5 @@ const env = require("../environment") const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants") -const { getAllApps } = require("@budibase/auth/db") const { sanitizeKey } = require("@budibase/auth/src/objectStore") const BB_CDN = "https://cdn.app.budi.live/assets" @@ -8,7 +7,6 @@ const BB_CDN = "https://cdn.app.budi.live/assets" exports.wait = ms => new Promise(resolve => setTimeout(resolve, ms)) exports.isDev = env.isDev -exports.getAllApps = getAllApps /** * Makes sure that a URL has the correct number of slashes, while maintaining the diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/admin/configs.js index 02dd18360..3135e6374 100644 --- a/packages/worker/src/api/controllers/admin/configs.js +++ b/packages/worker/src/api/controllers/admin/configs.js @@ -169,14 +169,14 @@ exports.destroy = async function (ctx) { } exports.configChecklist = async function (ctx) { - const tenantId = ctx.query.tenantId + const tenantId = ctx.request.query.tenantId const db = tenantId ? getGlobalDB(tenantId) : getGlobalDBFromCtx(ctx) try { // TODO: Watch get started video // Apps exist - const apps = (await getAllApps({ CouchDB })) + const apps = await getAllApps({ tenantId }) // They have set up SMTP const smtpConfig = await getScopedFullConfig(db, { diff --git a/packages/worker/src/api/controllers/admin/email.js b/packages/worker/src/api/controllers/admin/email.js index 67b45a110..11841d1b5 100644 --- a/packages/worker/src/api/controllers/admin/email.js +++ b/packages/worker/src/api/controllers/admin/email.js @@ -2,8 +2,16 @@ const { sendEmail } = require("../../../utilities/email") const { getGlobalDBFromCtx } = require("@budibase/auth/db") exports.sendEmail = async ctx => { - let { tenantId, workspaceId, email, userId, purpose, contents, from, subject } = - ctx.request.body + let { + tenantId, + workspaceId, + email, + userId, + purpose, + contents, + from, + subject, + } = ctx.request.body let user if (userId) { const db = getGlobalDBFromCtx(ctx) diff --git a/packages/worker/src/api/controllers/admin/roles.js b/packages/worker/src/api/controllers/admin/roles.js index 3cd99f8c4..b00741ead 100644 --- a/packages/worker/src/api/controllers/admin/roles.js +++ b/packages/worker/src/api/controllers/admin/roles.js @@ -7,8 +7,9 @@ const { const CouchDB = require("../../../db") exports.fetch = async ctx => { + const tenantId = ctx.user.tenantId // always use the dev apps as they'll be most up to date (true) - const apps = await getAllApps({ CouchDB, all: true }) + const apps = await getAllApps({ tenantId, all: true }) const promises = [] for (let app of apps) { // use dev app IDs diff --git a/packages/worker/src/api/controllers/admin/users.js b/packages/worker/src/api/controllers/admin/users.js index a27b42d59..ba7cb97d4 100644 --- a/packages/worker/src/api/controllers/admin/users.js +++ b/packages/worker/src/api/controllers/admin/users.js @@ -3,7 +3,7 @@ const { getGlobalUserParams, getGlobalDB, getGlobalDBFromCtx, - StaticDatabases + StaticDatabases, } = require("@budibase/auth/db") const { hash, getGlobalUserByEmail, newid } = require("@budibase/auth").utils const { UserStatus, EmailTemplatePurpose } = require("../../../constants") @@ -16,17 +16,17 @@ const CouchDB = require("../../../db") const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name const tenantDocId = StaticDatabases.PLATFORM_INFO.docs.tenants -async function noTenantsExist() { - const db = new CouchDB(PLATFORM_INFO_DB) - const tenants = await db.get(tenantDocId) - return !tenants || !tenants.tenantIds || tenants.tenantIds.length === 0 -} - async function tryAddTenant(tenantId) { const db = new CouchDB(PLATFORM_INFO_DB) - let tenants = await db.get(tenantDocId) + let tenants + try { + tenants = await db.get(tenantDocId) + } catch (err) { + // if theres an error don't worry, we'll just write it in + } if (!tenants || !Array.isArray(tenants.tenantIds)) { tenants = { + _id: tenantDocId, tenantIds: [], } } @@ -120,11 +120,18 @@ exports.save = async ctx => { } exports.adminUser = async ctx => { - if (!await noTenantsExist()) { + const { email, password, tenantId } = ctx.request.body + const db = getGlobalDB(tenantId) + const response = await db.allDocs( + getGlobalUserParams(null, { + include_docs: true, + }) + ) + + if (response.rows.some(row => row.doc.admin)) { ctx.throw(403, "You cannot initialise once an admin user has been created.") } - const { email, password } = ctx.request.body const user = { email: email, password: password, @@ -135,9 +142,10 @@ exports.adminUser = async ctx => { admin: { global: true, }, + tenantId, } try { - ctx.body = await saveUser(user, newid()) + ctx.body = await saveUser(user, tenantId) } catch (err) { ctx.throw(err.status || 400, err) } diff --git a/packages/worker/src/api/controllers/admin/workspaces.js b/packages/worker/src/api/controllers/admin/workspaces.js index 233f34576..e2910a236 100644 --- a/packages/worker/src/api/controllers/admin/workspaces.js +++ b/packages/worker/src/api/controllers/admin/workspaces.js @@ -1,5 +1,8 @@ -const { getWorkspaceParams, generateWorkspaceID, getGlobalDBFromCtx } = - require("@budibase/auth/db") +const { + getWorkspaceParams, + generateWorkspaceID, + getGlobalDBFromCtx, +} = require("@budibase/auth/db") exports.save = async function (ctx) { const db = getGlobalDBFromCtx(ctx) diff --git a/packages/worker/src/api/controllers/app.js b/packages/worker/src/api/controllers/app.js index 782c45bc1..fc3d3535c 100644 --- a/packages/worker/src/api/controllers/app.js +++ b/packages/worker/src/api/controllers/app.js @@ -1,10 +1,10 @@ const { getAllApps } = require("@budibase/auth/db") -const CouchDB = require("../../db") const URL_REGEX_SLASH = /\/|\\/g exports.getApps = async ctx => { - const apps = await getAllApps({ CouchDB }) + const tenantId = ctx.user.tenantId + const apps = await getAllApps({ tenantId }) const body = {} for (let app of apps) { diff --git a/packages/worker/src/api/routes/admin/auth.js b/packages/worker/src/api/routes/admin/auth.js index 3fef6d723..8a1ee76ae 100644 --- a/packages/worker/src/api/routes/admin/auth.js +++ b/packages/worker/src/api/routes/admin/auth.js @@ -29,8 +29,16 @@ function buildResetUpdateValidation() { } router - .post("/api/admin/auth/:tenantId/login", buildAuthValidation(), authController.authenticate) - .post("/api/admin/auth/:tenantId/reset", buildResetValidation(), authController.reset) + .post( + "/api/admin/auth/:tenantId/login", + buildAuthValidation(), + authController.authenticate + ) + .post( + "/api/admin/auth/:tenantId/reset", + buildResetValidation(), + authController.reset + ) .post( "/api/admin/auth/:tenantId/reset/update", buildResetUpdateValidation(), diff --git a/packages/worker/src/api/routes/admin/users.js b/packages/worker/src/api/routes/admin/users.js index e30272523..5eb70759e 100644 --- a/packages/worker/src/api/routes/admin/users.js +++ b/packages/worker/src/api/routes/admin/users.js @@ -11,6 +11,7 @@ function buildAdminInitValidation() { Joi.object({ email: Joi.string().required(), password: Joi.string().required(), + tenantId: Joi.string().required(), }) .required() .unknown(false) From c248844a24ea677b96378b482e7b71dc84023f71 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 16 Jul 2021 18:24:32 +0100 Subject: [PATCH 010/173] Adding organisation page. --- packages/auth/src/db/utils.js | 3 +- .../builder/src/pages/builder/_layout.svelte | 2 +- .../src/pages/builder/auth/index.svelte | 3 +- .../src/pages/builder/auth/login.svelte | 3 - .../builder/src/pages/builder/auth/org.svelte | 52 ++++------------- packages/builder/src/stores/portal/admin.js | 4 +- packages/builder/src/stores/portal/auth.js | 58 ++++++++++++------- .../src/api/controllers/admin/configs.js | 1 - .../worker/src/api/controllers/admin/users.js | 2 +- 9 files changed, 54 insertions(+), 74 deletions(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index fd2395c8f..628564904 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -210,7 +210,7 @@ exports.getAllApps = async ({ tenantId, dev, all } = {}) => { } else { const response = await Promise.allSettled(appPromises) const apps = response - .filter(result => result.status === "fulfilled" ) + .filter(result => result.status === "fulfilled") .map(({ value }) => value) .filter(app => { const appTenant = !app.tenantId ? DEFAULT_TENANT_ID : app.tenantId @@ -218,7 +218,6 @@ exports.getAllApps = async ({ tenantId, dev, all } = {}) => { }) if (!all) { return apps.filter(app => { - if (dev) { return isDevApp(app) } diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index 43a5d205d..cde1d332b 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -29,7 +29,7 @@ !$isActive("./invite") ) { const returnUrl = encodeURIComponent(window.location.pathname) - $redirect("./auth/login?", { returnUrl }) + $redirect("./auth?", { returnUrl }) } else if ($auth?.user?.forceResetPassword) { $redirect("./auth/reset") } diff --git a/packages/builder/src/pages/builder/auth/index.svelte b/packages/builder/src/pages/builder/auth/index.svelte index 12570aeeb..d335c3dfb 100644 --- a/packages/builder/src/pages/builder/auth/index.svelte +++ b/packages/builder/src/pages/builder/auth/index.svelte @@ -1,4 +1,5 @@ diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte index 88a87e173..9fb984c73 100644 --- a/packages/builder/src/pages/builder/auth/login.svelte +++ b/packages/builder/src/pages/builder/auth/login.svelte @@ -15,7 +15,6 @@ import Logo from "assets/bb-emblem.svg" import { onMount } from "svelte" - let tenantId = "" let username = "" let password = "" @@ -26,7 +25,6 @@ await auth.login({ username, password, - tenantId, }) notifications.success("Logged in successfully") if ($auth?.user?.forceResetPassword) { @@ -66,7 +64,6 @@ Sign in with email - import { - ActionButton, Body, Button, Divider, Heading, Input, Layout, - notifications, } from "@budibase/bbui" - import { goto, params } from "@roxi/routify" - import { auth, organisation } from "stores/portal" - import GoogleButton from "./_components/GoogleButton.svelte" + import { goto } from "@roxi/routify" + import { auth } from "stores/portal" import Logo from "assets/bb-emblem.svg" - import { onMount } from "svelte" let tenantId = "" - let username = "" - let password = "" - $: company = $organisation.company || "Budibase" - - async function login() { - try { - await auth.login({ - username, - password, - tenantId, - }) - notifications.success("Logged in successfully") - if ($auth?.user?.forceResetPassword) { - $goto("./reset") - } else { - if ($params["?returnUrl"]) { - window.location = decodeURIComponent($params["?returnUrl"]) - } else { - notifications.success("Logged in successfully") - $goto("../portal") - } - } - } catch (err) { - console.error(err) - notifications.error("Invalid credentials") - } + async function setOrg() { + auth.setOrg(tenantId) + $goto("./login") } function handleKeydown(evt) { - if (evt.key === "Enter") login() + if (evt.key === "Enter") setOrg() } - - onMount(async () => { - await organisation.init() - }) @@ -59,17 +28,16 @@
- logo - Sign in to {company} + logo + Set Budibase organisation - - Sign in with email + Set organisation - +
diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index dab56a922..1902e17a2 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -8,7 +8,9 @@ export function createAdminStore() { async function init() { try { const tenantId = get(auth).tenantId - const response = await api.get(`/api/admin/configs/checklist?tenantId=${tenantId}`) + const response = await api.get( + `/api/admin/configs/checklist?tenantId=${tenantId}` + ) const json = await response.json() const onboardingSteps = Object.keys(json) diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index 56762532d..608a0d91b 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -2,57 +2,71 @@ import { derived, writable, get } from "svelte/store" import api from "../../builderStore/api" export function createAuthStore() { - const user = writable(null) - const store = derived(user, $user => { + const auth = writable({ + user: null, + tenantId: "default", + }) + const store = derived(auth, $store => { let initials = null let isAdmin = false let isBuilder = false - let tenantId = "default" - if ($user) { - if ($user.firstName) { - initials = $user.firstName[0] - if ($user.lastName) { - initials += $user.lastName[0] + if ($store.user) { + const user = $store.user + if (user.firstName) { + initials = user.firstName[0] + if (user.lastName) { + initials += user.lastName[0] } - } else if ($user.email) { - initials = $user.email[0] + } else if (user.email) { + initials = user.email[0] } else { initials = "Unknown" } - isAdmin = !!$user.admin?.global - isBuilder = !!$user.builder?.global - tenantId = $user.tenantId || tenantId + isAdmin = !!user.admin?.global + isBuilder = !!user.builder?.global } return { - user: $user, + user: $store.user, + tenantId: $store.tenantId, initials, isAdmin, isBuilder, - tenantId, } }) + function setUser(user) { + auth.update(store => { + store.user = user + return store + }) + } + return { subscribe: store.subscribe, + setOrg: tenantId => { + auth.update(store => { + store.tenantId = tenantId + return store + }) + }, checkAuth: async () => { const response = await api.get("/api/admin/users/self") if (response.status !== 200) { - user.set(null) + setUser(null) } else { const json = await response.json() - user.set(json) + setUser(json) } }, login: async creds => { - const tenantId = creds.tenantId || get(store).tenantId - delete creds.tenantId + const tenantId = get(store).tenantId const response = await api.post( `/api/admin/auth/${tenantId}/login`, creds ) const json = await response.json() if (response.status === 200) { - user.set(json.user) + setUser(json.user) } else { throw "Invalid credentials" } @@ -64,13 +78,13 @@ export function createAuthStore() { throw "Unable to create logout" } await response.json() - user.set(null) + setUser(null) }, updateSelf: async fields => { const newUser = { ...get(user), ...fields } const response = await api.post("/api/admin/users/self", newUser) if (response.status === 200) { - user.set(newUser) + setUser(newUser) } else { throw "Unable to update user details" } diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/admin/configs.js index 3135e6374..9e1c2c353 100644 --- a/packages/worker/src/api/controllers/admin/configs.js +++ b/packages/worker/src/api/controllers/admin/configs.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../../db") const { generateConfigID, getConfigParams, diff --git a/packages/worker/src/api/controllers/admin/users.js b/packages/worker/src/api/controllers/admin/users.js index ba7cb97d4..a0e4e18e8 100644 --- a/packages/worker/src/api/controllers/admin/users.js +++ b/packages/worker/src/api/controllers/admin/users.js @@ -5,7 +5,7 @@ const { getGlobalDBFromCtx, StaticDatabases, } = require("@budibase/auth/db") -const { hash, getGlobalUserByEmail, newid } = require("@budibase/auth").utils +const { hash, getGlobalUserByEmail } = require("@budibase/auth").utils const { UserStatus, EmailTemplatePurpose } = require("../../../constants") const { checkInviteCode } = require("../../../utilities/redis") const { sendEmail } = require("../../../utilities/email") From 4a192b5a4bdca1f5a810d33da89672c2e9d4d78d Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 16 Jul 2021 18:26:17 +0100 Subject: [PATCH 011/173] Linting. --- packages/builder/src/pages/builder/auth/org.svelte | 9 +-------- packages/builder/src/stores/portal/auth.js | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/builder/src/pages/builder/auth/org.svelte b/packages/builder/src/pages/builder/auth/org.svelte index eae426f25..33692030b 100644 --- a/packages/builder/src/pages/builder/auth/org.svelte +++ b/packages/builder/src/pages/builder/auth/org.svelte @@ -1,12 +1,5 @@ diff --git a/packages/builder/src/pages/builder/auth/org.svelte b/packages/builder/src/pages/builder/auth/org.svelte index 33692030b..ee3f2b9d4 100644 --- a/packages/builder/src/pages/builder/auth/org.svelte +++ b/packages/builder/src/pages/builder/auth/org.svelte @@ -1,14 +1,16 @@
@@ -50,9 +57,16 @@ - + + + {#if multiTenancyEnabled} + + Change organisation + + {/if} +
diff --git a/packages/builder/src/pages/builder/auth/forgot.svelte b/packages/builder/src/pages/builder/auth/forgot.svelte index 85301b3f0..4cbd4c243 100644 --- a/packages/builder/src/pages/builder/auth/forgot.svelte +++ b/packages/builder/src/pages/builder/auth/forgot.svelte @@ -6,10 +6,12 @@ Layout, Body, Heading, + ActionButton, } from "@budibase/bbui" - import { organisation, auth } from "stores/portal" + import {organisation, auth} from "stores/portal" import Logo from "assets/bb-emblem.svg" - import { onMount } from "svelte" + import {onMount} from "svelte" + import {goto} from "@roxi/routify" let email = "" @@ -41,9 +43,14 @@ - + + + $goto("../")}> + Back + + diff --git a/packages/builder/src/pages/builder/auth/index.svelte b/packages/builder/src/pages/builder/auth/index.svelte index bbdd9949b..61c42ea08 100644 --- a/packages/builder/src/pages/builder/auth/index.svelte +++ b/packages/builder/src/pages/builder/auth/index.svelte @@ -9,7 +9,6 @@ let loaded = false $: { - console.log(loaded) if (loaded && multiTenancyEnabled && !tenantSet) { $redirect("./org") } else if (loaded) { diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte index 5e50c6a8c..66d8af589 100644 --- a/packages/builder/src/pages/builder/auth/login.svelte +++ b/packages/builder/src/pages/builder/auth/login.svelte @@ -10,7 +10,7 @@ notifications, } from "@budibase/bbui" import { goto, params } from "@roxi/routify" - import { auth, organisation, oidc } from "stores/portal" + import {auth, organisation, oidc, admin} from "stores/portal" import GoogleButton from "./_components/GoogleButton.svelte" import OIDCButton from "./_components/OIDCButton.svelte" import Logo from "assets/bb-emblem.svg" @@ -18,8 +18,10 @@ let username = "" let password = "" + let loaded = false $: company = $organisation.company || "Budibase" + $: multiTenancyEnabled = $admin.multiTenancy async function login() { try { @@ -49,6 +51,7 @@ onMount(async () => { await organisation.init() + loaded = true }) @@ -60,8 +63,10 @@ logo Sign in to {company} - - + {#if loaded} + + + {/if} Sign in with email @@ -78,9 +83,11 @@ $goto("./forgot")}> Forgot password? - $goto("./org")}> - Change organisation - + {#if multiTenancyEnabled} + $goto("./org")}> + Change organisation + + {/if} diff --git a/packages/builder/src/pages/builder/auth/org.svelte b/packages/builder/src/pages/builder/auth/org.svelte index ee3f2b9d4..166935a13 100644 --- a/packages/builder/src/pages/builder/auth/org.svelte +++ b/packages/builder/src/pages/builder/auth/org.svelte @@ -3,10 +3,14 @@ import { goto } from "@roxi/routify" import { auth, admin } from "stores/portal" import Logo from "assets/bb-emblem.svg" + import { get } from "svelte/store" - let tenantId = "" + let tenantId = get(auth).tenantSet ? get(auth).tenantId : "" async function setOrg() { + if (tenantId == null || tenantId === "") { + tenantId = "default" + } auth.setOrg(tenantId) // re-init now org selected await admin.init() diff --git a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte index 904c27ecb..c46b27a4d 100644 --- a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte @@ -248,8 +248,6 @@ const oidcResponse = await api.get(`/api/admin/configs/${ConfigTypes.OIDC}`) const oidcDoc = await oidcResponse.json() if (!oidcDoc._id) { - console.log("hi") - providers.oidc = { type: ConfigTypes.OIDC, config: { configs: [{ activated: true }] }, diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index 05a66138f..fed8e9e1b 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -52,7 +52,7 @@ export function createAuthStore() { setOrg: tenantId => { auth.update(store => { store.tenantId = tenantId - store.tenantSet = true + store.tenantSet = !!tenantId return store }) }, From 82465bdf7a206bcbc3e5a96713c94bada6f50dbc Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 21 Jul 2021 18:23:49 +0100 Subject: [PATCH 021/173] Adding query string functionality to skip org setup. --- .../src/pages/builder/auth/index.svelte | 1 + .../builder/src/pages/builder/auth/org.svelte | 5 +++++ .../builder/src/pages/builder/index.svelte | 8 ++++--- packages/builder/src/pages/index.svelte | 6 +++++- packages/builder/src/stores/portal/auth.js | 21 ++++++++++++++----- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/builder/src/pages/builder/auth/index.svelte b/packages/builder/src/pages/builder/auth/index.svelte index 61c42ea08..daeb56cb6 100644 --- a/packages/builder/src/pages/builder/auth/index.svelte +++ b/packages/builder/src/pages/builder/auth/index.svelte @@ -18,6 +18,7 @@ onMount(async () => { await admin.init() + auth.checkQueryString() loaded = true }) diff --git a/packages/builder/src/pages/builder/auth/org.svelte b/packages/builder/src/pages/builder/auth/org.svelte index 166935a13..caf31b265 100644 --- a/packages/builder/src/pages/builder/auth/org.svelte +++ b/packages/builder/src/pages/builder/auth/org.svelte @@ -4,6 +4,7 @@ import { auth, admin } from "stores/portal" import Logo from "assets/bb-emblem.svg" import { get } from "svelte/store" + import { onMount } from "svelte" let tenantId = get(auth).tenantSet ? get(auth).tenantId : "" @@ -20,6 +21,10 @@ function handleKeydown(evt) { if (evt.key === "Enter") setOrg() } + + onMount(() => { + auth.checkQueryString() + }) diff --git a/packages/builder/src/pages/builder/index.svelte b/packages/builder/src/pages/builder/index.svelte index a90e1c7f2..963870716 100644 --- a/packages/builder/src/pages/builder/index.svelte +++ b/packages/builder/src/pages/builder/index.svelte @@ -2,13 +2,15 @@ import { redirect } from "@roxi/routify" import { auth } from "stores/portal" + auth.checkQueryString() + $: { if (!$auth.user) { - $redirect("./auth") + $redirect(`./auth`) } else if ($auth.user.builder?.global) { - $redirect("./portal") + $redirect(`./portal}`) } else { - $redirect("./apps") + $redirect(`./apps`) } } diff --git a/packages/builder/src/pages/index.svelte b/packages/builder/src/pages/index.svelte index 4c97b4976..0c083391a 100644 --- a/packages/builder/src/pages/index.svelte +++ b/packages/builder/src/pages/index.svelte @@ -1,4 +1,8 @@ diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index fed8e9e1b..17e9d11d3 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -47,14 +47,25 @@ export function createAuthStore() { }) } + function setOrganisation(tenantId) { + auth.update(store => { + store.tenantId = tenantId + store.tenantSet = !!tenantId + return store + }) + } + return { subscribe: store.subscribe, + checkQueryString: () => { + const urlParams = new URLSearchParams(window.location.search) + if (urlParams.has("tenantId")) { + const tenantId = urlParams.get("tenantId") + setOrganisation(tenantId) + } + }, setOrg: tenantId => { - auth.update(store => { - store.tenantId = tenantId - store.tenantSet = !!tenantId - return store - }) + setOrganisation(tenantId) }, checkAuth: async () => { const response = await api.get("/api/admin/users/self") From 8121528f76b197736bffa25a6ee9f13f37fec95a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 21 Jul 2021 18:24:58 +0100 Subject: [PATCH 022/173] Linting. --- packages/builder/src/pages/builder/auth/forgot.svelte | 10 ++++------ packages/builder/src/pages/builder/auth/login.svelte | 2 +- .../src/pages/builder/portal/manage/auth/index.svelte | 6 ++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/builder/src/pages/builder/auth/forgot.svelte b/packages/builder/src/pages/builder/auth/forgot.svelte index 4cbd4c243..2503a99eb 100644 --- a/packages/builder/src/pages/builder/auth/forgot.svelte +++ b/packages/builder/src/pages/builder/auth/forgot.svelte @@ -8,10 +8,10 @@ Heading, ActionButton, } from "@budibase/bbui" - import {organisation, auth} from "stores/portal" + import { organisation, auth } from "stores/portal" import Logo from "assets/bb-emblem.svg" - import {onMount} from "svelte" - import {goto} from "@roxi/routify" + import { onMount } from "svelte" + import { goto } from "@roxi/routify" let email = "" @@ -47,9 +47,7 @@ - $goto("../")}> - Back - + $goto("../")}>Back diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte index 66d8af589..2dc3781e3 100644 --- a/packages/builder/src/pages/builder/auth/login.svelte +++ b/packages/builder/src/pages/builder/auth/login.svelte @@ -10,7 +10,7 @@ notifications, } from "@budibase/bbui" import { goto, params } from "@roxi/routify" - import {auth, organisation, oidc, admin} from "stores/portal" + import { auth, organisation, oidc, admin } from "stores/portal" import GoogleButton from "./_components/GoogleButton.svelte" import OIDCButton from "./_components/OIDCButton.svelte" import Logo from "assets/bb-emblem.svg" diff --git a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte index c46b27a4d..0c89528c9 100644 --- a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte @@ -113,15 +113,13 @@ // Create a flag so that it will only try to save completed forms $: partialGoogle = - providers.google?.config?.clientID || - providers.google?.config?.clientSecret + providers.google?.config?.clientID || providers.google?.config?.clientSecret $: partialOidc = providers.oidc?.config?.configs[0].configUrl || providers.oidc?.config?.configs[0].clientID || providers.oidc?.config?.configs[0].clientSecret $: googleComplete = - providers.google?.config?.clientID && - providers.google?.config?.clientSecret + providers.google?.config?.clientID && providers.google?.config?.clientSecret $: oidcComplete = providers.oidc?.config?.configs[0].configUrl && providers.oidc?.config?.configs[0].clientID && From 190798d4eea37991405548ebc1b28619b730a204 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 12:56:47 +0100 Subject: [PATCH 023/173] Swapping admin -> global in worker to reduce confusion in API url naming. --- hosting/envoy.dev.yaml.hbs | 2 +- hosting/envoy.yaml | 2 +- .../middleware/passport/tests/google.spec.js | 2 +- .../src/pages/builder/admin/index.svelte | 2 +- .../auth/_components/GoogleButton.svelte | 2 +- .../auth/_components/OIDCButton.svelte | 2 +- .../builder/portal/manage/auth/index.svelte | 16 +++++------ .../builder/portal/manage/email/index.svelte | 4 +-- .../portal/manage/users/[userId].svelte | 4 +-- .../portal/settings/organisation.svelte | 2 +- packages/builder/src/stores/portal/admin.js | 4 +-- packages/builder/src/stores/portal/auth.js | 14 +++++----- packages/builder/src/stores/portal/email.js | 6 ++-- packages/builder/src/stores/portal/oidc.js | 2 +- .../builder/src/stores/portal/organisation.js | 4 +-- packages/builder/src/stores/portal/users.js | 12 ++++---- packages/client/src/api/auth.js | 4 +-- packages/server/__mocks__/node-fetch.ts | 2 +- packages/server/src/api/controllers/dev.js | 3 +- packages/server/src/api/routes/dev.js | 6 ++-- .../server/src/utilities/workerRequests.js | 10 +++---- .../api/controllers/{admin => global}/auth.js | 6 ++-- .../controllers/{admin => global}/configs.js | 2 +- .../controllers/{admin => global}/email.js | 0 .../controllers/{admin => global}/roles.js | 0 .../controllers/{admin => global}/sessions.js | 0 .../{admin => global}/templates.js | 0 .../controllers/{admin => global}/tenants.js | 0 .../controllers/{admin => global}/users.js | 4 +-- .../{admin => global}/workspaces.js | 0 packages/worker/src/api/index.js | 14 +++++----- packages/worker/src/api/routes/admin/roles.js | 11 -------- .../worker/src/api/routes/admin/sessions.js | 14 ---------- .../worker/src/api/routes/admin/tenants.js | 12 -------- .../src/api/routes/{admin => global}/auth.js | 18 ++++++------ .../api/routes/{admin => global}/configs.js | 20 ++++++------- .../src/api/routes/{admin => global}/email.js | 4 +-- .../worker/src/api/routes/global/roles.js | 11 ++++++++ .../worker/src/api/routes/global/sessions.js | 14 ++++++++++ .../api/routes/{admin => global}/templates.js | 16 +++++------ .../worker/src/api/routes/global/tenants.js | 12 ++++++++ .../src/api/routes/{admin => global}/users.js | 28 +++++++++---------- .../routes/{admin => global}/workspaces.js | 10 +++---- packages/worker/src/api/routes/index.js | 18 ++++++------ .../worker/src/api/routes/tests/auth.spec.js | 12 ++++---- .../src/api/routes/tests/configs.spec.js | 4 +-- .../worker/src/api/routes/tests/email.spec.js | 4 +-- .../src/api/routes/tests/realEmail.spec.js | 4 +-- .../worker/src/api/routes/tests/users.spec.js | 6 ++-- .../api/routes/tests/utilities/controllers.js | 10 +++---- 50 files changed, 180 insertions(+), 179 deletions(-) rename packages/worker/src/api/controllers/{admin => global}/auth.js (95%) rename packages/worker/src/api/controllers/{admin => global}/configs.js (99%) rename packages/worker/src/api/controllers/{admin => global}/email.js (100%) rename packages/worker/src/api/controllers/{admin => global}/roles.js (100%) rename packages/worker/src/api/controllers/{admin => global}/sessions.js (100%) rename packages/worker/src/api/controllers/{admin => global}/templates.js (100%) rename packages/worker/src/api/controllers/{admin => global}/tenants.js (100%) rename packages/worker/src/api/controllers/{admin => global}/users.js (98%) rename packages/worker/src/api/controllers/{admin => global}/workspaces.js (100%) delete mode 100644 packages/worker/src/api/routes/admin/roles.js delete mode 100644 packages/worker/src/api/routes/admin/sessions.js delete mode 100644 packages/worker/src/api/routes/admin/tenants.js rename packages/worker/src/api/routes/{admin => global}/auth.js (65%) rename packages/worker/src/api/routes/{admin => global}/configs.js (84%) rename packages/worker/src/api/routes/{admin => global}/email.js (89%) create mode 100644 packages/worker/src/api/routes/global/roles.js create mode 100644 packages/worker/src/api/routes/global/sessions.js rename packages/worker/src/api/routes/{admin => global}/templates.js (66%) create mode 100644 packages/worker/src/api/routes/global/tenants.js rename packages/worker/src/api/routes/{admin => global}/users.js (75%) rename packages/worker/src/api/routes/{admin => global}/workspaces.js (75%) diff --git a/hosting/envoy.dev.yaml.hbs b/hosting/envoy.dev.yaml.hbs index 76417b3e0..5e876a236 100644 --- a/hosting/envoy.dev.yaml.hbs +++ b/hosting/envoy.dev.yaml.hbs @@ -26,7 +26,7 @@ static_resources: cluster: couchdb-service prefix_rewrite: "/" - - match: { prefix: "/api/admin/" } + - match: { prefix: "/api/global/" } route: cluster: worker-dev diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index d7b34f4d5..dc90f1105 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -38,7 +38,7 @@ static_resources: cluster: app-service # special case for worker admin API - - match: { prefix: "/api/admin/" } + - match: { prefix: "/api/global/" } route: cluster: worker-service diff --git a/packages/auth/src/middleware/passport/tests/google.spec.js b/packages/auth/src/middleware/passport/tests/google.spec.js index 0e2d3d96e..e753a23b6 100644 --- a/packages/auth/src/middleware/passport/tests/google.spec.js +++ b/packages/auth/src/middleware/passport/tests/google.spec.js @@ -29,7 +29,7 @@ describe("google", () => { it("should create successfully create a google strategy", async () => { const google = require("../google") - await google.strategyFactory(googleConfig, `/api/admin/auth/${TENANT_ID}/google/callback`) + await google.strategyFactory(googleConfig, `/api/global/auth/${TENANT_ID}/google/callback`) const expectedOptions = { clientID: googleConfig.clientID, diff --git a/packages/builder/src/pages/builder/admin/index.svelte b/packages/builder/src/pages/builder/admin/index.svelte index 870492c17..abacb86b8 100644 --- a/packages/builder/src/pages/builder/admin/index.svelte +++ b/packages/builder/src/pages/builder/admin/index.svelte @@ -24,7 +24,7 @@ try { adminUser.tenantId = tenantId // Save the admin user - const response = await api.post(`/api/admin/users/init`, adminUser) + const response = await api.post(`/api/global/users/init`, adminUser) const json = await response.json() if (response.status !== 200) { throw new Error(json.message) diff --git a/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte b/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte index 09f05ab4b..6962b61f9 100644 --- a/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte +++ b/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte @@ -11,7 +11,7 @@ {#if show} window.open(`/api/admin/auth/${tenantId}/google`, "_blank")} + on:click={() => window.open(`/api/global/auth/${tenantId}/google`, "_blank")} >
google icon diff --git a/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte b/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte index 22ecad162..24aca0c39 100644 --- a/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte +++ b/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte @@ -31,7 +31,7 @@ {#if show} - window.open(`/api/admin/auth/oidc/configs/${$oidc.uuid}`, "_blank")} + window.open(`/api/global/auth/oidc/configs/${$oidc.uuid}`, "_blank")} >
oidc icon diff --git a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte index 0c89528c9..678aea049 100644 --- a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte @@ -39,7 +39,7 @@ name: "callbackURL", label: "Callback URL", readonly: true, - placeholder: `/api/admin/auth/${tenantId}/google/callback`, + placeholder: `/api/global/auth/${tenantId}/google/callback`, }, ], } @@ -53,7 +53,7 @@ name: "callbackURL", label: "Callback URL", readonly: true, - placeholder: `/api/admin/auth/${tenantId}/oidc/callback`, + placeholder: `/api/global/auth/${tenantId}/oidc/callback`, }, ], } @@ -129,7 +129,7 @@ let data = new FormData() data.append("file", file) const res = await api.post( - `/api/admin/configs/upload/logos_oidc/${file.name}`, + `/api/global/configs/upload/logos_oidc/${file.name}`, data, {} ) @@ -163,7 +163,7 @@ `Please fill in all required ${ConfigTypes.OIDC} fields` ) } else { - calls.push(api.post(`/api/admin/configs`, element)) + calls.push(api.post(`/api/global/configs`, element)) // turn the save button grey when clicked oidcSaveButtonDisabled = true originalOidcDoc = cloneDeep(providers.oidc) @@ -178,7 +178,7 @@ ) } else { delete element.config.callbackURL - calls.push(api.post(`/api/admin/configs`, element)) + calls.push(api.post(`/api/global/configs`, element)) googleSaveButtonDisabled = true originalGoogleDoc = cloneDeep(providers.google) } @@ -211,7 +211,7 @@ await organisation.init() // fetch the configs for oauth const googleResponse = await api.get( - `/api/admin/configs/${ConfigTypes.Google}` + `/api/global/configs/${ConfigTypes.Google}` ) const googleDoc = await googleResponse.json() @@ -228,7 +228,7 @@ //Get the list of user uploaded logos and push it to the dropdown options. //This needs to be done before the config call so they're available when the dropdown renders - const res = await api.get(`/api/admin/configs/logos_oidc`) + const res = await api.get(`/api/global/configs/logos_oidc`) const configSettings = await res.json() if (configSettings.config) { @@ -243,7 +243,7 @@ }) }) } - const oidcResponse = await api.get(`/api/admin/configs/${ConfigTypes.OIDC}`) + const oidcResponse = await api.get(`/api/global/configs/${ConfigTypes.OIDC}`) const oidcDoc = await oidcResponse.json() if (!oidcDoc._id) { providers.oidc = { diff --git a/packages/builder/src/pages/builder/portal/manage/email/index.svelte b/packages/builder/src/pages/builder/portal/manage/email/index.svelte index f45f4ea29..027eba4ef 100644 --- a/packages/builder/src/pages/builder/portal/manage/email/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/email/index.svelte @@ -53,7 +53,7 @@ delete smtp.config.auth } // Save your SMTP config - const response = await api.post(`/api/admin/configs`, smtp) + const response = await api.post(`/api/global/configs`, smtp) if (response.status !== 200) { const error = await response.text() @@ -75,7 +75,7 @@ async function fetchSmtp() { loading = true // fetch the configs for smtp - const smtpResponse = await api.get(`/api/admin/configs/${ConfigTypes.SMTP}`) + const smtpResponse = await api.get(`/api/global/configs/${ConfigTypes.SMTP}`) const smtpDoc = await smtpResponse.json() if (!smtpDoc._id) { diff --git a/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte b/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte index 8e029d73b..43360ddf5 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte @@ -45,8 +45,8 @@ }) let selectedApp - const userFetch = fetchData(`/api/admin/users/${userId}`) - const apps = fetchData(`/api/admin/roles`) + const userFetch = fetchData(`/api/global/users/${userId}`) + const apps = fetchData(`/api/global/roles`) async function deleteUser() { const res = await users.delete(userId) diff --git a/packages/builder/src/pages/builder/portal/settings/organisation.svelte b/packages/builder/src/pages/builder/portal/settings/organisation.svelte index 682b0c4ee..b274d3af9 100644 --- a/packages/builder/src/pages/builder/portal/settings/organisation.svelte +++ b/packages/builder/src/pages/builder/portal/settings/organisation.svelte @@ -37,7 +37,7 @@ async function uploadLogo(file) { let data = new FormData() data.append("file", file) - const res = await post("/api/admin/configs/upload/settings/logo", data, {}) + const res = await post("/api/global/configs/upload/settings/logo", data, {}) return await res.json() } diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index ef39914ce..2985bd23f 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -9,7 +9,7 @@ export function createAdminStore() { try { const tenantId = get(auth).tenantId const response = await api.get( - `/api/admin/configs/checklist?tenantId=${tenantId}` + `/api/global/configs/checklist?tenantId=${tenantId}` ) const json = await response.json() @@ -38,7 +38,7 @@ export function createAdminStore() { async function multiTenancyEnabled() { let enabled = false try { - const response = await api.get(`/api/admin/tenants/enabled`) + const response = await api.get(`/api/global/tenants/enabled`) const json = await response.json() enabled = json.enabled } catch (err) { diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index 17e9d11d3..aa856d4a2 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -68,7 +68,7 @@ export function createAuthStore() { setOrganisation(tenantId) }, checkAuth: async () => { - const response = await api.get("/api/admin/users/self") + const response = await api.get("/api/global/users/self") if (response.status !== 200) { setUser(null) } else { @@ -79,7 +79,7 @@ export function createAuthStore() { login: async creds => { const tenantId = get(store).tenantId const response = await api.post( - `/api/admin/auth/${tenantId}/login`, + `/api/global/auth/${tenantId}/login`, creds ) const json = await response.json() @@ -91,7 +91,7 @@ export function createAuthStore() { return json }, logout: async () => { - const response = await api.post(`/api/admin/auth/logout`) + const response = await api.post(`/api/global/auth/logout`) if (response.status !== 200) { throw "Unable to create logout" } @@ -100,7 +100,7 @@ export function createAuthStore() { }, updateSelf: async fields => { const newUser = { ...get(auth).user, ...fields } - const response = await api.post("/api/admin/users/self", newUser) + const response = await api.post("/api/global/users/self", newUser) if (response.status === 200) { setUser(newUser) } else { @@ -109,7 +109,7 @@ export function createAuthStore() { }, forgotPassword: async email => { const tenantId = get(store).tenantId - const response = await api.post(`/api/admin/auth/${tenantId}/reset`, { + const response = await api.post(`/api/global/auth/${tenantId}/reset`, { email, }) if (response.status !== 200) { @@ -120,7 +120,7 @@ export function createAuthStore() { resetPassword: async (password, code) => { const tenantId = get(store).tenantId const response = await api.post( - `/api/admin/auth/${tenantId}/reset/update`, + `/api/global/auth/${tenantId}/reset/update`, { password, resetCode: code, @@ -132,7 +132,7 @@ export function createAuthStore() { await response.json() }, createUser: async user => { - const response = await api.post(`/api/admin/users`, user) + const response = await api.post(`/api/global/users`, user) if (response.status !== 200) { throw "Unable to create user" } diff --git a/packages/builder/src/stores/portal/email.js b/packages/builder/src/stores/portal/email.js index 4ec6d72d3..a01548014 100644 --- a/packages/builder/src/stores/portal/email.js +++ b/packages/builder/src/stores/portal/email.js @@ -9,11 +9,11 @@ export function createEmailStore() { templates: { fetch: async () => { // fetch the email template definitions - const response = await api.get(`/api/admin/template/definitions`) + const response = await api.get(`/api/global/template/definitions`) const definitions = await response.json() // fetch the email templates themselves - const templatesResponse = await api.get(`/api/admin/template/email`) + const templatesResponse = await api.get(`/api/global/template/email`) const templates = await templatesResponse.json() store.set({ @@ -23,7 +23,7 @@ export function createEmailStore() { }, save: async template => { // Save your template config - const response = await api.post(`/api/admin/template`, template) + const response = await api.post(`/api/global/template`, template) const json = await response.json() if (response.status !== 200) throw new Error(json.message) template._rev = json._rev diff --git a/packages/builder/src/stores/portal/oidc.js b/packages/builder/src/stores/portal/oidc.js index a30a8534d..3e3a7048c 100644 --- a/packages/builder/src/stores/portal/oidc.js +++ b/packages/builder/src/stores/portal/oidc.js @@ -15,7 +15,7 @@ export function createOidcStore() { async function init() { const tenantId = get(auth).tenantId const res = await api.get( - `/api/admin/configs/public/oidc?tenantId=${tenantId}` + `/api/global/configs/public/oidc?tenantId=${tenantId}` ) const json = await res.json() diff --git a/packages/builder/src/stores/portal/organisation.js b/packages/builder/src/stores/portal/organisation.js index aaa2c2e8e..03bfa6ca2 100644 --- a/packages/builder/src/stores/portal/organisation.js +++ b/packages/builder/src/stores/portal/organisation.js @@ -17,7 +17,7 @@ export function createOrganisationStore() { async function init() { const tenantId = get(auth).tenantId - const res = await api.get(`/api/admin/configs/public?tenantId=${tenantId}`) + const res = await api.get(`/api/global/configs/public?tenantId=${tenantId}`) const json = await res.json() if (json.status === 400) { @@ -28,7 +28,7 @@ export function createOrganisationStore() { } async function save(config) { - const res = await api.post("/api/admin/configs", { + const res = await api.post("/api/global/configs", { type: "settings", config: { ...get(store), ...config }, _rev: get(store)._rev, diff --git a/packages/builder/src/stores/portal/users.js b/packages/builder/src/stores/portal/users.js index 8a19f7980..17299dc05 100644 --- a/packages/builder/src/stores/portal/users.js +++ b/packages/builder/src/stores/portal/users.js @@ -6,7 +6,7 @@ export function createUsersStore() { const { subscribe, set } = writable([]) async function init() { - const response = await api.get(`/api/admin/users`) + const response = await api.get(`/api/global/users`) const json = await response.json() set(json) } @@ -23,12 +23,12 @@ export function createUsersStore() { global: true, } } - const response = await api.post(`/api/admin/users/invite`, body) + const response = await api.post(`/api/global/users/invite`, body) return await response.json() } async function acceptInvite(inviteCode, password) { - const response = await api.post("/api/admin/users/invite/accept", { + const response = await api.post("/api/global/users/invite/accept", { inviteCode, password, }) @@ -47,20 +47,20 @@ export function createUsersStore() { if (admin) { body.admin = { global: true } } - const response = await api.post("/api/admin/users", body) + const response = await api.post("/api/global/users", body) await init() return await response.json() } async function del(id) { - const response = await api.delete(`/api/admin/users/${id}`) + const response = await api.delete(`/api/global/users/${id}`) update(users => users.filter(user => user._id !== id)) return await response.json() } async function save(data) { try { - const res = await post(`/api/admin/users`, data) + const res = await post(`/api/global/users`, data) return await res.json() } catch (error) { console.log(error) diff --git a/packages/client/src/api/auth.js b/packages/client/src/api/auth.js index 6ea105d9f..68ca5dbc8 100644 --- a/packages/client/src/api/auth.js +++ b/packages/client/src/api/auth.js @@ -13,7 +13,7 @@ export const logIn = async ({ email, password }) => { return API.error("Please enter your password") } return await API.post({ - url: "/api/admin/auth", + url: "/api/global/auth", body: { username: email, password }, }) } @@ -23,7 +23,7 @@ export const logIn = async ({ email, password }) => { */ export const fetchSelf = async () => { const user = await API.get({ url: "/api/self" }) - if (user?._id) { + if (user && user._id) { if (user.roleId === "PUBLIC") { // Don't try to enrich a public user as it will 403 return user diff --git a/packages/server/__mocks__/node-fetch.ts b/packages/server/__mocks__/node-fetch.ts index eaac41285..dfb839fe8 100644 --- a/packages/server/__mocks__/node-fetch.ts +++ b/packages/server/__mocks__/node-fetch.ts @@ -16,7 +16,7 @@ module FetchMock { } } - if (url.includes("/api/admin")) { + if (url.includes("/api/global")) { return json({ email: "test@test.com", _id: "us_test@test.com", diff --git a/packages/server/src/api/controllers/dev.js b/packages/server/src/api/controllers/dev.js index 6dcd5727f..d75c4032d 100644 --- a/packages/server/src/api/controllers/dev.js +++ b/packages/server/src/api/controllers/dev.js @@ -9,8 +9,9 @@ const { DocumentTypes } = require("../../db/utils") async function redirect(ctx, method) { const { devPath } = ctx.params + const queryString = ctx.originalUrl.split("?")[1] || "" const response = await fetch( - checkSlashesInUrl(`${env.WORKER_URL}/api/admin/${devPath}`), + checkSlashesInUrl(`${env.WORKER_URL}/api/global/${devPath}?${queryString}`), request( ctx, { diff --git a/packages/server/src/api/routes/dev.js b/packages/server/src/api/routes/dev.js index cd4c6e8fd..7612d332d 100644 --- a/packages/server/src/api/routes/dev.js +++ b/packages/server/src/api/routes/dev.js @@ -8,9 +8,9 @@ const router = Router() if (env.isDev() || env.isTest()) { router - .get("/api/admin/:devPath(.*)", controller.redirectGet) - .post("/api/admin/:devPath(.*)", controller.redirectPost) - .delete("/api/admin/:devPath(.*)", controller.redirectDelete) + .get("/api/global/:devPath(.*)", controller.redirectGet) + .post("/api/global/:devPath(.*)", controller.redirectPost) + .delete("/api/global/:devPath(.*)", controller.redirectDelete) } router diff --git a/packages/server/src/utilities/workerRequests.js b/packages/server/src/utilities/workerRequests.js index d56111385..cdbac4cb4 100644 --- a/packages/server/src/utilities/workerRequests.js +++ b/packages/server/src/utilities/workerRequests.js @@ -30,7 +30,7 @@ exports.request = request exports.sendSmtpEmail = async (tenantId, to, from, subject, contents) => { const response = await fetch( - checkSlashesInUrl(env.WORKER_URL + `/api/admin/email/send`), + checkSlashesInUrl(env.WORKER_URL + `/api/global/email/send`), request(null, { method: "POST", body: { @@ -74,7 +74,7 @@ exports.getDeployedApps = async ctx => { } exports.getGlobalSelf = async (ctx, appId = null) => { - const endpoint = `/api/admin/users/self` + const endpoint = `/api/global/users/self` const response = await fetch( checkSlashesInUrl(env.WORKER_URL + endpoint), // we don't want to use API key when getting self @@ -96,11 +96,11 @@ exports.addAppRoleToUser = async (ctx, appId, roleId, userId = null) => { body = {} if (!userId) { user = await exports.getGlobalSelf(ctx) - endpoint = `/api/admin/users/self` + endpoint = `/api/global/users/self` } else { user = await getGlobalUser(ctx, appId, userId) body._id = userId - endpoint = `/api/admin/users` + endpoint = `/api/global/users` } body = { ...body, @@ -125,7 +125,7 @@ exports.addAppRoleToUser = async (ctx, appId, roleId, userId = null) => { exports.removeAppFromUserRoles = async (ctx, appId) => { const deployedAppId = getDeployedAppID(appId) const response = await fetch( - checkSlashesInUrl(env.WORKER_URL + `/api/admin/roles/${deployedAppId}`), + checkSlashesInUrl(env.WORKER_URL + `/api/global/roles/${deployedAppId}`), request(ctx, { method: "DELETE", }) diff --git a/packages/worker/src/api/controllers/admin/auth.js b/packages/worker/src/api/controllers/global/auth.js similarity index 95% rename from packages/worker/src/api/controllers/admin/auth.js rename to packages/worker/src/api/controllers/global/auth.js index c6c1641ab..82faea986 100644 --- a/packages/worker/src/api/controllers/admin/auth.js +++ b/packages/worker/src/api/controllers/global/auth.js @@ -103,7 +103,7 @@ exports.logout = async ctx => { exports.googlePreAuth = async (ctx, next) => { const tenantId = ctx.params.tenantId const db = getGlobalDB(tenantId) - const callbackUrl = `/api/admin/auth/${tenantId}/google/callback` + const callbackUrl = `/api/global/auth/${tenantId}/google/callback` const config = await authPkg.db.getScopedConfig(db, { type: Configs.GOOGLE, @@ -119,7 +119,7 @@ exports.googlePreAuth = async (ctx, next) => { exports.googleAuth = async (ctx, next) => { const tenantId = ctx.params.tenantId const db = getGlobalDB(tenantId) - const callbackUrl = `/api/admin/auth/${tenantId}/google/callback` + const callbackUrl = `/api/global/auth/${tenantId}/google/callback` const config = await authPkg.db.getScopedConfig(db, { type: Configs.GOOGLE, @@ -148,7 +148,7 @@ async function oidcStrategyFactory(ctx, configId) { const chosenConfig = config.configs.filter(c => c.uuid === configId)[0] - const callbackUrl = `${ctx.protocol}://${ctx.host}/api/admin/auth/${tenantId}/oidc/callback` + const callbackUrl = `${ctx.protocol}://${ctx.host}/api/global/auth/${tenantId}/oidc/callback` return oidc.strategyFactory(chosenConfig, callbackUrl) } diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/global/configs.js similarity index 99% rename from packages/worker/src/api/controllers/admin/configs.js rename to packages/worker/src/api/controllers/global/configs.js index 519ab517c..cd79d7f95 100644 --- a/packages/worker/src/api/controllers/admin/configs.js +++ b/packages/worker/src/api/controllers/global/configs.js @@ -230,7 +230,7 @@ exports.configChecklist = async function (ctx) { const oidcConfig = await getScopedFullConfig(db, { type: Configs.OIDC, }) - // They have set up an admin user + // They have set up an global user const users = await db.allDocs( getGlobalUserParams(null, { include_docs: true, diff --git a/packages/worker/src/api/controllers/admin/email.js b/packages/worker/src/api/controllers/global/email.js similarity index 100% rename from packages/worker/src/api/controllers/admin/email.js rename to packages/worker/src/api/controllers/global/email.js diff --git a/packages/worker/src/api/controllers/admin/roles.js b/packages/worker/src/api/controllers/global/roles.js similarity index 100% rename from packages/worker/src/api/controllers/admin/roles.js rename to packages/worker/src/api/controllers/global/roles.js diff --git a/packages/worker/src/api/controllers/admin/sessions.js b/packages/worker/src/api/controllers/global/sessions.js similarity index 100% rename from packages/worker/src/api/controllers/admin/sessions.js rename to packages/worker/src/api/controllers/global/sessions.js diff --git a/packages/worker/src/api/controllers/admin/templates.js b/packages/worker/src/api/controllers/global/templates.js similarity index 100% rename from packages/worker/src/api/controllers/admin/templates.js rename to packages/worker/src/api/controllers/global/templates.js diff --git a/packages/worker/src/api/controllers/admin/tenants.js b/packages/worker/src/api/controllers/global/tenants.js similarity index 100% rename from packages/worker/src/api/controllers/admin/tenants.js rename to packages/worker/src/api/controllers/global/tenants.js diff --git a/packages/worker/src/api/controllers/admin/users.js b/packages/worker/src/api/controllers/global/users.js similarity index 98% rename from packages/worker/src/api/controllers/admin/users.js rename to packages/worker/src/api/controllers/global/users.js index 321f58975..8706bcdcf 100644 --- a/packages/worker/src/api/controllers/admin/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -149,7 +149,7 @@ exports.adminUser = async ctx => { ) if (response.rows.some(row => row.doc.admin)) { - ctx.throw(403, "You cannot initialise once an admin user has been created.") + ctx.throw(403, "You cannot initialise once an global user has been created.") } const user = { @@ -280,7 +280,7 @@ exports.invite = async ctx => { exports.inviteAccept = async ctx => { const { inviteCode, password, firstName, lastName } = ctx.request.body try { - // info is an extension of the user object that was stored by admin + // info is an extension of the user object that was stored by global const { email, info } = await checkInviteCode(inviteCode) // only pass through certain props for accepting ctx.request.body = { diff --git a/packages/worker/src/api/controllers/admin/workspaces.js b/packages/worker/src/api/controllers/global/workspaces.js similarity index 100% rename from packages/worker/src/api/controllers/admin/workspaces.js rename to packages/worker/src/api/controllers/global/workspaces.js diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js index c63896d32..8d4f8c5bc 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -7,33 +7,33 @@ const { buildAuthMiddleware, auditLog } = require("@budibase/auth").auth const PUBLIC_ENDPOINTS = [ { // this covers all of the POST auth routes - route: "/api/admin/auth/:tenantId", + route: "/api/global/auth/:tenantId", method: "POST", }, { // this covers all of the GET auth routes - route: "/api/admin/auth/:tenantId", + route: "/api/global/auth/:tenantId", method: "GET", }, { // this covers all of the public config routes - route: "/api/admin/configs/public", + route: "/api/global/configs/public", method: "GET", }, { - route: "api/admin/tenants/enabled", + route: "api/global/tenants/enabled", method: "GET", }, { - route: "/api/admin/configs/checklist", + route: "/api/global/configs/checklist", method: "GET", }, { - route: "/api/admin/users/init", + route: "/api/global/users/init", method: "POST", }, { - route: "/api/admin/users/invite/accept", + route: "/api/global/users/invite/accept", method: "POST", }, ] diff --git a/packages/worker/src/api/routes/admin/roles.js b/packages/worker/src/api/routes/admin/roles.js deleted file mode 100644 index 2deef6b3f..000000000 --- a/packages/worker/src/api/routes/admin/roles.js +++ /dev/null @@ -1,11 +0,0 @@ -const Router = require("@koa/router") -const controller = require("../../controllers/admin/roles") -const adminOnly = require("../../../middleware/adminOnly") - -const router = Router() - -router - .get("/api/admin/roles", adminOnly, controller.fetch) - .get("/api/admin/roles/:appId", adminOnly, controller.find) - -module.exports = router diff --git a/packages/worker/src/api/routes/admin/sessions.js b/packages/worker/src/api/routes/admin/sessions.js deleted file mode 100644 index 9cf5f58f8..000000000 --- a/packages/worker/src/api/routes/admin/sessions.js +++ /dev/null @@ -1,14 +0,0 @@ -const Router = require("@koa/router") -const controller = require("../../controllers/admin/sessions") -const adminOnly = require("../../../middleware/adminOnly") - -const router = Router() - -router - .get("/api/admin/sessions", adminOnly, controller.fetch) - .get("/api/admin/sessions/self", controller.selfSessions) - .get("/api/admin/sessions/:userId", adminOnly, controller.find) - .delete("/api/admin/sessions/:userId", adminOnly, controller.invalidateUser) - .delete("/api/admin/sessions/self/:sessionId", controller.invalidateSession) - -module.exports = router diff --git a/packages/worker/src/api/routes/admin/tenants.js b/packages/worker/src/api/routes/admin/tenants.js deleted file mode 100644 index a9c479dca..000000000 --- a/packages/worker/src/api/routes/admin/tenants.js +++ /dev/null @@ -1,12 +0,0 @@ -const Router = require("@koa/router") -const controller = require("../../controllers/admin/tenants") -const adminOnly = require("../../../middleware/adminOnly") - -const router = Router() - -router - .get("/api/admin/tenants/enabled", controller.multiTenancyEnabled) - .get("/api/admin/tenants/:tenantId/exists", controller.exists) - .get("/api/admin/tenants", adminOnly, controller.fetch) - -module.exports = router diff --git a/packages/worker/src/api/routes/admin/auth.js b/packages/worker/src/api/routes/global/auth.js similarity index 65% rename from packages/worker/src/api/routes/admin/auth.js rename to packages/worker/src/api/routes/global/auth.js index 4be2ef1c9..b7b560cb6 100644 --- a/packages/worker/src/api/routes/admin/auth.js +++ b/packages/worker/src/api/routes/global/auth.js @@ -1,5 +1,5 @@ const Router = require("@koa/router") -const authController = require("../../controllers/admin/auth") +const authController = require("../../controllers/global/auth") const joiValidator = require("../../../middleware/joi-validator") const Joi = require("joi") @@ -30,27 +30,27 @@ function buildResetUpdateValidation() { router .post( - "/api/admin/auth/:tenantId/login", + "/api/global/auth/:tenantId/login", buildAuthValidation(), authController.authenticate ) .post( - "/api/admin/auth/:tenantId/reset", + "/api/global/auth/:tenantId/reset", buildResetValidation(), authController.reset ) .post( - "/api/admin/auth/:tenantId/reset/update", + "/api/global/auth/:tenantId/reset/update", buildResetUpdateValidation(), authController.resetUpdate ) - .post("/api/admin/auth/logout", authController.logout) - .get("/api/admin/auth/:tenantId/google", authController.googlePreAuth) - .get("/api/admin/auth/:tenantId/google/callback", authController.googleAuth) + .post("/api/global/auth/logout", authController.logout) + .get("/api/global/auth/:tenantId/google", authController.googlePreAuth) + .get("/api/global/auth/:tenantId/google/callback", authController.googleAuth) .get( - "/api/admin/auth/:tenantId/oidc/configs/:configId", + "/api/global/auth/:tenantId/oidc/configs/:configId", authController.oidcPreAuth ) - .get("/api/admin/auth/:tenantId/oidc/callback", authController.oidcAuth) + .get("/api/global/auth/:tenantId/oidc/callback", authController.oidcAuth) module.exports = router diff --git a/packages/worker/src/api/routes/admin/configs.js b/packages/worker/src/api/routes/global/configs.js similarity index 84% rename from packages/worker/src/api/routes/admin/configs.js rename to packages/worker/src/api/routes/global/configs.js index 840201cbd..f6cac4d3b 100644 --- a/packages/worker/src/api/routes/admin/configs.js +++ b/packages/worker/src/api/routes/global/configs.js @@ -1,5 +1,5 @@ const Router = require("@koa/router") -const controller = require("../../controllers/admin/configs") +const controller = require("../../controllers/global/configs") const joiValidator = require("../../../middleware/joi-validator") const adminOnly = require("../../../middleware/adminOnly") const Joi = require("joi") @@ -96,24 +96,24 @@ function buildConfigGetValidation() { router .post( - "/api/admin/configs", + "/api/global/configs", adminOnly, buildConfigSaveValidation(), controller.save ) - .delete("/api/admin/configs/:id/:rev", adminOnly, controller.destroy) - .get("/api/admin/configs", controller.fetch) - .get("/api/admin/configs/checklist", controller.configChecklist) + .delete("/api/global/configs/:id/:rev", adminOnly, controller.destroy) + .get("/api/global/configs", controller.fetch) + .get("/api/global/configs/checklist", controller.configChecklist) .get( - "/api/admin/configs/all/:type", + "/api/global/configs/all/:type", buildConfigGetValidation(), controller.fetch ) - .get("/api/admin/configs/public", controller.publicSettings) - .get("/api/admin/configs/public/oidc", controller.publicOidc) - .get("/api/admin/configs/:type", buildConfigGetValidation(), controller.find) + .get("/api/global/configs/public", controller.publicSettings) + .get("/api/global/configs/public/oidc", controller.publicOidc) + .get("/api/global/configs/:type", buildConfigGetValidation(), controller.find) .post( - "/api/admin/configs/upload/:type/:name", + "/api/global/configs/upload/:type/:name", adminOnly, buildUploadValidation(), controller.upload diff --git a/packages/worker/src/api/routes/admin/email.js b/packages/worker/src/api/routes/global/email.js similarity index 89% rename from packages/worker/src/api/routes/admin/email.js rename to packages/worker/src/api/routes/global/email.js index 0412f9c7c..fecbc02cd 100644 --- a/packages/worker/src/api/routes/admin/email.js +++ b/packages/worker/src/api/routes/global/email.js @@ -1,5 +1,5 @@ const Router = require("@koa/router") -const controller = require("../../controllers/admin/email") +const controller = require("../../controllers/global/email") const { EmailTemplatePurpose } = require("../../../constants") const joiValidator = require("../../../middleware/joi-validator") const adminOnly = require("../../../middleware/adminOnly") @@ -20,7 +20,7 @@ function buildEmailSendValidation() { } router.post( - "/api/admin/email/send", + "/api/global/email/send", buildEmailSendValidation(), adminOnly, controller.sendEmail diff --git a/packages/worker/src/api/routes/global/roles.js b/packages/worker/src/api/routes/global/roles.js new file mode 100644 index 000000000..c73fb317c --- /dev/null +++ b/packages/worker/src/api/routes/global/roles.js @@ -0,0 +1,11 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/global/roles") +const adminOnly = require("../../../middleware/adminOnly") + +const router = Router() + +router + .get("/api/global/roles", adminOnly, controller.fetch) + .get("/api/global/roles/:appId", adminOnly, controller.find) + +module.exports = router diff --git a/packages/worker/src/api/routes/global/sessions.js b/packages/worker/src/api/routes/global/sessions.js new file mode 100644 index 000000000..5ba6747e6 --- /dev/null +++ b/packages/worker/src/api/routes/global/sessions.js @@ -0,0 +1,14 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/global/sessions") +const adminOnly = require("../../../middleware/adminOnly") + +const router = Router() + +router + .get("/api/global/sessions", adminOnly, controller.fetch) + .get("/api/global/sessions/self", controller.selfSessions) + .get("/api/global/sessions/:userId", adminOnly, controller.find) + .delete("/api/global/sessions/:userId", adminOnly, controller.invalidateUser) + .delete("/api/global/sessions/self/:sessionId", controller.invalidateSession) + +module.exports = router diff --git a/packages/worker/src/api/routes/admin/templates.js b/packages/worker/src/api/routes/global/templates.js similarity index 66% rename from packages/worker/src/api/routes/admin/templates.js rename to packages/worker/src/api/routes/global/templates.js index 52ab24878..e4580d444 100644 --- a/packages/worker/src/api/routes/admin/templates.js +++ b/packages/worker/src/api/routes/global/templates.js @@ -1,5 +1,5 @@ const Router = require("@koa/router") -const controller = require("../../controllers/admin/templates") +const controller = require("../../controllers/global/templates") const joiValidator = require("../../../middleware/joi-validator") const Joi = require("joi") const { TemplatePurpose, TemplateTypes } = require("../../../constants") @@ -21,17 +21,17 @@ function buildTemplateSaveValidation() { } router - .get("/api/admin/template/definitions", controller.definitions) + .get("/api/global/template/definitions", controller.definitions) .post( - "/api/admin/template", + "/api/global/template", adminOnly, buildTemplateSaveValidation(), controller.save ) - .get("/api/admin/template", controller.fetch) - .get("/api/admin/template/:type", controller.fetchByType) - .get("/api/admin/template/:ownerId", controller.fetchByOwner) - .get("/api/admin/template/:id", controller.find) - .delete("/api/admin/template/:id/:rev", adminOnly, controller.destroy) + .get("/api/global/template", controller.fetch) + .get("/api/global/template/:type", controller.fetchByType) + .get("/api/global/template/:ownerId", controller.fetchByOwner) + .get("/api/global/template/:id", controller.find) + .delete("/api/global/template/:id/:rev", adminOnly, controller.destroy) module.exports = router diff --git a/packages/worker/src/api/routes/global/tenants.js b/packages/worker/src/api/routes/global/tenants.js new file mode 100644 index 000000000..0d1dc47af --- /dev/null +++ b/packages/worker/src/api/routes/global/tenants.js @@ -0,0 +1,12 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/global/tenants") +const adminOnly = require("../../../middleware/adminOnly") + +const router = Router() + +router + .get("/api/global/tenants/enabled", controller.multiTenancyEnabled) + .get("/api/global/tenants/:tenantId/exists", controller.exists) + .get("/api/global/tenants", adminOnly, controller.fetch) + +module.exports = router diff --git a/packages/worker/src/api/routes/admin/users.js b/packages/worker/src/api/routes/global/users.js similarity index 75% rename from packages/worker/src/api/routes/admin/users.js rename to packages/worker/src/api/routes/global/users.js index 5eb70759e..8be81c493 100644 --- a/packages/worker/src/api/routes/admin/users.js +++ b/packages/worker/src/api/routes/global/users.js @@ -1,5 +1,5 @@ const Router = require("@koa/router") -const controller = require("../../controllers/admin/users") +const controller = require("../../controllers/global/users") const joiValidator = require("../../../middleware/joi-validator") const adminOnly = require("../../../middleware/adminOnly") const Joi = require("joi") @@ -62,39 +62,39 @@ function buildInviteAcceptValidation() { router .post( - "/api/admin/users", + "/api/global/users", adminOnly, buildUserSaveValidation(), controller.save ) - .get("/api/admin/users", adminOnly, controller.fetch) - .delete("/api/admin/roles/:appId", adminOnly, controller.removeAppRole) - .delete("/api/admin/users/:id", adminOnly, controller.destroy) - .get("/api/admin/roles/:appId") + .get("/api/global/users", adminOnly, controller.fetch) + .delete("/api/global/roles/:appId", adminOnly, controller.removeAppRole) + .delete("/api/global/users/:id", adminOnly, controller.destroy) + .get("/api/global/roles/:appId") .post( - "/api/admin/users/invite", + "/api/global/users/invite", adminOnly, buildInviteValidation(), controller.invite ) - // non-admin endpoints + // non-global endpoints .post( - "/api/admin/users/self", + "/api/global/users/self", buildUserSaveValidation(true), controller.updateSelf ) .post( - "/api/admin/users/invite/accept", + "/api/global/users/invite/accept", buildInviteAcceptValidation(), controller.inviteAccept ) .post( - "/api/admin/users/init", + "/api/global/users/init", buildAdminInitValidation(), controller.adminUser ) - .get("/api/admin/users/self", controller.getSelf) - // admin endpoint but needs to come at end (blocks other endpoints otherwise) - .get("/api/admin/users/:id", adminOnly, controller.find) + .get("/api/global/users/self", controller.getSelf) + // global endpoint but needs to come at end (blocks other endpoints otherwise) + .get("/api/global/users/:id", adminOnly, controller.find) module.exports = router diff --git a/packages/worker/src/api/routes/admin/workspaces.js b/packages/worker/src/api/routes/global/workspaces.js similarity index 75% rename from packages/worker/src/api/routes/admin/workspaces.js rename to packages/worker/src/api/routes/global/workspaces.js index 72c3593ed..cab76b776 100644 --- a/packages/worker/src/api/routes/admin/workspaces.js +++ b/packages/worker/src/api/routes/global/workspaces.js @@ -1,5 +1,5 @@ const Router = require("@koa/router") -const controller = require("../../controllers/admin/workspaces") +const controller = require("../../controllers/global/workspaces") const joiValidator = require("../../../middleware/joi-validator") const adminOnly = require("../../../middleware/adminOnly") const Joi = require("joi") @@ -26,13 +26,13 @@ function buildWorkspaceSaveValidation() { router .post( - "/api/admin/workspaces", + "/api/global/workspaces", adminOnly, buildWorkspaceSaveValidation(), controller.save ) - .delete("/api/admin/workspaces/:id", adminOnly, controller.destroy) - .get("/api/admin/workspaces", controller.fetch) - .get("/api/admin/workspaces/:id", controller.find) + .delete("/api/global/workspaces/:id", adminOnly, controller.destroy) + .get("/api/global/workspaces", controller.fetch) + .get("/api/global/workspaces/:id", controller.find) module.exports = router diff --git a/packages/worker/src/api/routes/index.js b/packages/worker/src/api/routes/index.js index a4f2c852d..ddccd3db2 100644 --- a/packages/worker/src/api/routes/index.js +++ b/packages/worker/src/api/routes/index.js @@ -1,12 +1,12 @@ -const userRoutes = require("./admin/users") -const configRoutes = require("./admin/configs") -const workspaceRoutes = require("./admin/workspaces") -const templateRoutes = require("./admin/templates") -const tenantsRoutes = require("./admin/tenants") -const emailRoutes = require("./admin/email") -const authRoutes = require("./admin/auth") -const roleRoutes = require("./admin/roles") -const sessionRoutes = require("./admin/sessions") +const userRoutes = require("./global/users") +const configRoutes = require("./global/configs") +const workspaceRoutes = require("./global/workspaces") +const templateRoutes = require("./global/templates") +const tenantsRoutes = require("./global/tenants") +const emailRoutes = require("./global/email") +const authRoutes = require("./global/auth") +const roleRoutes = require("./global/roles") +const sessionRoutes = require("./global/sessions") const appRoutes = require("./app") exports.routes = [ diff --git a/packages/worker/src/api/routes/tests/auth.spec.js b/packages/worker/src/api/routes/tests/auth.spec.js index 227185ae3..f55e7ac8b 100644 --- a/packages/worker/src/api/routes/tests/auth.spec.js +++ b/packages/worker/src/api/routes/tests/auth.spec.js @@ -5,7 +5,7 @@ const TENANT_ID = "default" jest.mock("nodemailer") const sendMailMock = setup.emailMock() -describe("/api/admin/auth", () => { +describe("/api/global/auth", () => { let request = setup.getRequest() let config = setup.getConfig() let code @@ -26,7 +26,7 @@ describe("/api/admin/auth", () => { await config.saveSettingsConfig() await config.createUser("test@test.com") const res = await request - .post(`/api/admin/auth/${TENANT_ID}/reset`) + .post(`/api/global/auth/${TENANT_ID}/reset`) .send({ email: "test@test.com", }) @@ -43,7 +43,7 @@ describe("/api/admin/auth", () => { it("should allow resetting user password with code", async () => { const res = await request - .post(`/api/admin/auth/${TENANT_ID}/reset/update`) + .post(`/api/global/auth/${TENANT_ID}/reset/update`) .send({ password: "newpassword", resetCode: code, @@ -76,13 +76,13 @@ describe("/api/admin/auth", () => { afterEach(() => { expect(strategyFactory).toBeCalledWith( chosenConfig, - `http://127.0.0.1:4003/api/admin/auth/${TENANT_ID}/oidc/callback` // calculated url + `http://127.0.0.1:4003/api/global/auth/${TENANT_ID}/oidc/callback` // calculated url ) }) describe("oidc configs", () => { it("should load strategy and delegate to passport", async () => { - await request.get(`/api/admin/auth/${TENANT_ID}/oidc/configs/${configId}`) + await request.get(`/api/global/auth/${TENANT_ID}/oidc/configs/${configId}`) expect(passportSpy).toBeCalledWith(mockStrategyReturn, { scope: ["profile", "email"], @@ -93,7 +93,7 @@ describe("/api/admin/auth", () => { describe("oidc callback", () => { it("should load strategy and delegate to passport", async () => { - await request.get(`/api/admin/auth/${TENANT_ID}/oidc/callback`) + await request.get(`/api/global/auth/${TENANT_ID}/oidc/callback`) .set(config.getOIDConfigCookie(configId)) expect(passportSpy).toBeCalledWith(mockStrategyReturn, { diff --git a/packages/worker/src/api/routes/tests/configs.spec.js b/packages/worker/src/api/routes/tests/configs.spec.js index 13ba2bd3b..285cddec4 100644 --- a/packages/worker/src/api/routes/tests/configs.spec.js +++ b/packages/worker/src/api/routes/tests/configs.spec.js @@ -8,7 +8,7 @@ nodemailer.createTransport.mockReturnValue({ verify: jest.fn() }) -describe("/api/admin/configs/checklist", () => { +describe("/api/global/configs/checklist", () => { let request = setup.getRequest() let config = setup.getConfig() @@ -24,7 +24,7 @@ describe("/api/admin/configs/checklist", () => { await config.saveSmtpConfig() const res = await request - .get(`/api/admin/configs/checklist`) + .get(`/api/global/configs/checklist`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) diff --git a/packages/worker/src/api/routes/tests/email.spec.js b/packages/worker/src/api/routes/tests/email.spec.js index 797b0326e..027ad83fc 100644 --- a/packages/worker/src/api/routes/tests/email.spec.js +++ b/packages/worker/src/api/routes/tests/email.spec.js @@ -10,7 +10,7 @@ nodemailer.createTransport.mockReturnValue({ verify: jest.fn() }) -describe("/api/admin/email", () => { +describe("/api/global/email", () => { let request = setup.getRequest() let config = setup.getConfig() @@ -25,7 +25,7 @@ describe("/api/admin/email", () => { await config.saveSmtpConfig() await config.saveSettingsConfig() const res = await request - .post(`/api/admin/email/send`) + .post(`/api/global/email/send`) .send({ email: "test@test.com", purpose: EmailTemplatePurpose.INVITATION, diff --git a/packages/worker/src/api/routes/tests/realEmail.spec.js b/packages/worker/src/api/routes/tests/realEmail.spec.js index acc0c7acc..845e31d91 100644 --- a/packages/worker/src/api/routes/tests/realEmail.spec.js +++ b/packages/worker/src/api/routes/tests/realEmail.spec.js @@ -6,7 +6,7 @@ const fetch = require("node-fetch") // need a longer timeout for getting these jest.setTimeout(30000) -describe("/api/admin/email", () => { +describe("/api/global/email", () => { let request = setup.getRequest() let config = setup.getConfig() @@ -21,7 +21,7 @@ describe("/api/admin/email", () => { await config.saveSettingsConfig() const user = await config.getUser("test@test.com") const res = await request - .post(`/api/admin/email/send`) + .post(`/api/global/email/send`) .send({ email: "test@test.com", purpose, diff --git a/packages/worker/src/api/routes/tests/users.spec.js b/packages/worker/src/api/routes/tests/users.spec.js index bf5b67ab1..b75364180 100644 --- a/packages/worker/src/api/routes/tests/users.spec.js +++ b/packages/worker/src/api/routes/tests/users.spec.js @@ -3,7 +3,7 @@ const setup = require("./utilities") jest.mock("nodemailer") const sendMailMock = setup.emailMock() -describe("/api/admin/users", () => { +describe("/api/global/users", () => { let request = setup.getRequest() let config = setup.getConfig() let code @@ -19,7 +19,7 @@ describe("/api/admin/users", () => { await config.saveSmtpConfig() await config.saveSettingsConfig() const res = await request - .post(`/api/admin/users/invite`) + .post(`/api/global/users/invite`) .send({ email: "invite@test.com", }) @@ -37,7 +37,7 @@ describe("/api/admin/users", () => { it("should be able to create new user from invite", async () => { const res = await request - .post(`/api/admin/users/invite/accept`) + .post(`/api/global/users/invite/accept`) .send({ password: "newpassword", inviteCode: code, diff --git a/packages/worker/src/api/routes/tests/utilities/controllers.js b/packages/worker/src/api/routes/tests/utilities/controllers.js index 869cf96d5..45216ae63 100644 --- a/packages/worker/src/api/routes/tests/utilities/controllers.js +++ b/packages/worker/src/api/routes/tests/utilities/controllers.js @@ -1,7 +1,7 @@ module.exports = { - email: require("../../../controllers/admin/email"), - workspaces: require("../../../controllers/admin/workspaces"), - config: require("../../../controllers/admin/configs"), - templates: require("../../../controllers/admin/templates"), - users: require("../../../controllers/admin/users"), + email: require("../../../controllers/global/email"), + workspaces: require("../../../controllers/global/workspaces"), + config: require("../../../controllers/global/configs"), + templates: require("../../../controllers/global/templates"), + users: require("../../../controllers/global/users"), } From 853b7b6fdc2fe73f32827593d9092f80cf9ebd4b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 14:29:30 +0100 Subject: [PATCH 024/173] Linting. --- .../src/pages/builder/auth/_components/GoogleButton.svelte | 3 ++- .../src/pages/builder/portal/manage/auth/index.svelte | 4 +++- .../src/pages/builder/portal/manage/email/index.svelte | 4 +++- packages/worker/src/api/controllers/global/users.js | 5 ++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte b/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte index 6962b61f9..0acaa127c 100644 --- a/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte +++ b/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte @@ -11,7 +11,8 @@ {#if show} window.open(`/api/global/auth/${tenantId}/google`, "_blank")} + on:click={() => + window.open(`/api/global/auth/${tenantId}/google`, "_blank")} >
google icon diff --git a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte index 678aea049..bcbdfe646 100644 --- a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte @@ -243,7 +243,9 @@ }) }) } - const oidcResponse = await api.get(`/api/global/configs/${ConfigTypes.OIDC}`) + const oidcResponse = await api.get( + `/api/global/configs/${ConfigTypes.OIDC}` + ) const oidcDoc = await oidcResponse.json() if (!oidcDoc._id) { providers.oidc = { diff --git a/packages/builder/src/pages/builder/portal/manage/email/index.svelte b/packages/builder/src/pages/builder/portal/manage/email/index.svelte index 027eba4ef..6e40442d4 100644 --- a/packages/builder/src/pages/builder/portal/manage/email/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/email/index.svelte @@ -75,7 +75,9 @@ async function fetchSmtp() { loading = true // fetch the configs for smtp - const smtpResponse = await api.get(`/api/global/configs/${ConfigTypes.SMTP}`) + const smtpResponse = await api.get( + `/api/global/configs/${ConfigTypes.SMTP}` + ) const smtpDoc = await smtpResponse.json() if (!smtpDoc._id) { diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index 8706bcdcf..a9d761bce 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -149,7 +149,10 @@ exports.adminUser = async ctx => { ) if (response.rows.some(row => row.doc.admin)) { - ctx.throw(403, "You cannot initialise once an global user has been created.") + ctx.throw( + 403, + "You cannot initialise once an global user has been created." + ) } const user = { From f2e713f5af0ef84b3a4951b311c1e33711bf1ca6 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 15:26:14 +0100 Subject: [PATCH 025/173] Adding fallbacks for oidc and google. --- .../builder/portal/manage/auth/index.svelte | 16 +++++++++--- .../worker/src/api/controllers/global/auth.js | 26 ++++++++++++++----- .../src/api/controllers/global/tenants.js | 2 +- packages/worker/src/api/routes/global/auth.js | 8 ++++++ 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte index bcbdfe646..2ca0f7e2e 100644 --- a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte @@ -21,16 +21,26 @@ } from "@budibase/bbui" import { onMount } from "svelte" import api from "builderStore/api" - import { organisation, auth } from "stores/portal" + import { organisation, auth, admin } from "stores/portal" import { uuid } from "builderStore/uuid" $: tenantId = $auth.tenantId + $: multiTenancyEnabled = $admin.multiTenancy const ConfigTypes = { Google: "google", OIDC: "oidc", } + function callbackUrl(tenantId, end) { + let url = `/api/global/auth` + if (multiTenancyEnabled && tenantId) { + url += `/${tenantId}` + } + url += end + return url + } + $: GoogleConfigFields = { Google: [ { name: "clientID", label: "Client ID" }, @@ -39,7 +49,7 @@ name: "callbackURL", label: "Callback URL", readonly: true, - placeholder: `/api/global/auth/${tenantId}/google/callback`, + placeholder: callbackUrl(tenantId, "/google/callback"), }, ], } @@ -53,7 +63,7 @@ name: "callbackURL", label: "Callback URL", readonly: true, - placeholder: `/api/global/auth/${tenantId}/oidc/callback`, + placeholder: callbackUrl(tenantId, "/oidc/callback"), }, ], } diff --git a/packages/worker/src/api/controllers/global/auth.js b/packages/worker/src/api/controllers/global/auth.js index 82faea986..c3bbaf41a 100644 --- a/packages/worker/src/api/controllers/global/auth.js +++ b/packages/worker/src/api/controllers/global/auth.js @@ -10,6 +10,15 @@ const { passport } = authPkg.auth const { checkResetPasswordCode } = require("../../../utilities/redis") const { getGlobalDB } = authPkg.db +function googleCallbackUrl(tenantId = null) { + let callbackUrl = `/api/global/auth` + if (tenantId) { + callbackUrl += `/${tenantId}` + } + callbackUrl += `/google/callback` + return callbackUrl +} + async function authInternal(ctx, user, err = null, info = null) { if (err) { console.error("Authentication error", err) @@ -101,9 +110,9 @@ exports.logout = async ctx => { * On a successful login, you will be redirected to the googleAuth callback route. */ exports.googlePreAuth = async (ctx, next) => { - const tenantId = ctx.params.tenantId + const tenantId = ctx.params ? ctx.params.tenantId : null const db = getGlobalDB(tenantId) - const callbackUrl = `/api/global/auth/${tenantId}/google/callback` + let callbackUrl = googleCallbackUrl(tenantId) const config = await authPkg.db.getScopedConfig(db, { type: Configs.GOOGLE, @@ -117,9 +126,9 @@ exports.googlePreAuth = async (ctx, next) => { } exports.googleAuth = async (ctx, next) => { - const tenantId = ctx.params.tenantId + const tenantId = ctx.params ? ctx.params.tenantId : null const db = getGlobalDB(tenantId) - const callbackUrl = `/api/global/auth/${tenantId}/google/callback` + const callbackUrl = googleCallbackUrl(tenantId) const config = await authPkg.db.getScopedConfig(db, { type: Configs.GOOGLE, @@ -139,7 +148,7 @@ exports.googleAuth = async (ctx, next) => { } async function oidcStrategyFactory(ctx, configId) { - const tenantId = ctx.params.tenantId + const tenantId = ctx.params ? ctx.params.tenantId : null const db = getGlobalDB(ctx.params.tenantId) const config = await authPkg.db.getScopedConfig(db, { type: Configs.OIDC, @@ -148,8 +157,11 @@ async function oidcStrategyFactory(ctx, configId) { const chosenConfig = config.configs.filter(c => c.uuid === configId)[0] - const callbackUrl = `${ctx.protocol}://${ctx.host}/api/global/auth/${tenantId}/oidc/callback` - + let callbackUrl = `${ctx.protocol}://${ctx.host}/api/global/auth` + if (tenantId) { + callbackUrl += `/${tenantId}` + } + callbackUrl += `/oidc/callback` return oidc.strategyFactory(chosenConfig, callbackUrl) } diff --git a/packages/worker/src/api/controllers/global/tenants.js b/packages/worker/src/api/controllers/global/tenants.js index ddf8930dd..677ba5a20 100644 --- a/packages/worker/src/api/controllers/global/tenants.js +++ b/packages/worker/src/api/controllers/global/tenants.js @@ -4,7 +4,7 @@ const { StaticDatabases } = require("@budibase/auth/db") exports.multiTenancyEnabled = async ctx => { ctx.body = { - enabled: !!env.MULTI_TENANCY, + enabled: false, } } diff --git a/packages/worker/src/api/routes/global/auth.js b/packages/worker/src/api/routes/global/auth.js index b7b560cb6..19a61df2e 100644 --- a/packages/worker/src/api/routes/global/auth.js +++ b/packages/worker/src/api/routes/global/auth.js @@ -52,5 +52,13 @@ router authController.oidcPreAuth ) .get("/api/global/auth/:tenantId/oidc/callback", authController.oidcAuth) + // deprecated - used by the default system before tenancy + .get("/api/global/auth/google", authController.googlePreAuth) + .get("/api/global/auth/google/callback", authController.googleAuth) + .get( + "/api/global/auth/oidc/configs/:configId", + authController.oidcPreAuth + ) + .get("/api/global/auth/oidc/callback", authController.oidcAuth) module.exports = router From 0473c295d3bb1d70dd036904fb54ee56227fe3d5 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 16:36:21 +0100 Subject: [PATCH 026/173] Changing how flags are handled. --- packages/builder/src/stores/portal/admin.js | 4 ++-- .../server/scripts/integrations/postgres/init.sql | 11 ++++++----- packages/worker/src/api/controllers/global/flags.js | 7 +++++++ packages/worker/src/api/controllers/global/tenants.js | 7 ------- packages/worker/src/api/index.js | 2 +- packages/worker/src/api/routes/global/flags.js | 9 +++++++++ packages/worker/src/api/routes/global/tenants.js | 1 - packages/worker/src/api/routes/index.js | 2 ++ 8 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 packages/worker/src/api/controllers/global/flags.js create mode 100644 packages/worker/src/api/routes/global/flags.js diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index 2985bd23f..96e67ea84 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -38,9 +38,9 @@ export function createAdminStore() { async function multiTenancyEnabled() { let enabled = false try { - const response = await api.get(`/api/global/tenants/enabled`) + const response = await api.get(`/api/global/flags`) const json = await response.json() - enabled = json.enabled + enabled = json.multiTenancy } catch (err) { // just let it stay disabled } diff --git a/packages/server/scripts/integrations/postgres/init.sql b/packages/server/scripts/integrations/postgres/init.sql index 5a99520c1..cc2fc734f 100644 --- a/packages/server/scripts/integrations/postgres/init.sql +++ b/packages/server/scripts/integrations/postgres/init.sql @@ -10,10 +10,11 @@ CREATE TABLE Persons ( CREATE TABLE Tasks ( TaskID SERIAL PRIMARY KEY, PersonID INT, + Completed BOOLEAN, TaskName varchar(255), CONSTRAINT fkPersons FOREIGN KEY(PersonID) - REFERENCES Persons(PersonID) + REFERENCES Persons(PersonID) ); CREATE TABLE Products ( ProductID SERIAL PRIMARY KEY, @@ -24,15 +25,15 @@ CREATE TABLE Products_Tasks ( TaskID INT NOT NULL, CONSTRAINT fkProducts FOREIGN KEY(ProductID) - REFERENCES Products(ProductID), + REFERENCES Products(ProductID), CONSTRAINT fkTasks FOREIGN KEY(TaskID) - REFERENCES Tasks(TaskID), + REFERENCES Tasks(TaskID), PRIMARY KEY (ProductID, TaskID) ); INSERT INTO Persons (FirstName, LastName, Address, City) VALUES ('Mike', 'Hughes', '123 Fake Street', 'Belfast'); -INSERT INTO Tasks (PersonID, TaskName) VALUES (1, 'assembling'); -INSERT INTO Tasks (PersonID, TaskName) VALUES (1, 'processing'); +INSERT INTO Tasks (PersonID, TaskName, Completed) VALUES (1, 'assembling', TRUE); +INSERT INTO Tasks (PersonID, TaskName, Completed) VALUES (1, 'processing', FALSE); INSERT INTO Products (ProductName) VALUES ('Computers'); INSERT INTO Products (ProductName) VALUES ('Laptops'); INSERT INTO Products (ProductName) VALUES ('Chairs'); diff --git a/packages/worker/src/api/controllers/global/flags.js b/packages/worker/src/api/controllers/global/flags.js new file mode 100644 index 000000000..26e998e66 --- /dev/null +++ b/packages/worker/src/api/controllers/global/flags.js @@ -0,0 +1,7 @@ +const env = require("../../../environment") + +exports.fetch = async ctx => { + ctx.body = { + multiTenancy: !!env.MULTI_TENANCY, + } +} \ No newline at end of file diff --git a/packages/worker/src/api/controllers/global/tenants.js b/packages/worker/src/api/controllers/global/tenants.js index 677ba5a20..e053216dd 100644 --- a/packages/worker/src/api/controllers/global/tenants.js +++ b/packages/worker/src/api/controllers/global/tenants.js @@ -1,13 +1,6 @@ -const env = require("../../../environment") const CouchDB = require("../../../db") const { StaticDatabases } = require("@budibase/auth/db") -exports.multiTenancyEnabled = async ctx => { - ctx.body = { - enabled: false, - } -} - exports.exists = async ctx => { const tenantId = ctx.request.params const db = new CouchDB(StaticDatabases.PLATFORM_INFO.name) diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js index 8d4f8c5bc..cfeb3506e 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -21,7 +21,7 @@ const PUBLIC_ENDPOINTS = [ method: "GET", }, { - route: "api/global/tenants/enabled", + route: "api/global/flags", method: "GET", }, { diff --git a/packages/worker/src/api/routes/global/flags.js b/packages/worker/src/api/routes/global/flags.js new file mode 100644 index 000000000..5f63455db --- /dev/null +++ b/packages/worker/src/api/routes/global/flags.js @@ -0,0 +1,9 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/global/flags") + +const router = Router() + +router + .get("/api/global/flags", controller.fetch) + +module.exports = router \ No newline at end of file diff --git a/packages/worker/src/api/routes/global/tenants.js b/packages/worker/src/api/routes/global/tenants.js index 0d1dc47af..432e32711 100644 --- a/packages/worker/src/api/routes/global/tenants.js +++ b/packages/worker/src/api/routes/global/tenants.js @@ -5,7 +5,6 @@ const adminOnly = require("../../../middleware/adminOnly") const router = Router() router - .get("/api/global/tenants/enabled", controller.multiTenancyEnabled) .get("/api/global/tenants/:tenantId/exists", controller.exists) .get("/api/global/tenants", adminOnly, controller.fetch) diff --git a/packages/worker/src/api/routes/index.js b/packages/worker/src/api/routes/index.js index ddccd3db2..6341dffeb 100644 --- a/packages/worker/src/api/routes/index.js +++ b/packages/worker/src/api/routes/index.js @@ -7,6 +7,7 @@ const emailRoutes = require("./global/email") const authRoutes = require("./global/auth") const roleRoutes = require("./global/roles") const sessionRoutes = require("./global/sessions") +const flagRoutes = require("./global/flags") const appRoutes = require("./app") exports.routes = [ @@ -20,4 +21,5 @@ exports.routes = [ emailRoutes, sessionRoutes, roleRoutes, + flagRoutes, ] From 8bf275ca44a0feb96a6f851bece34ed69b7e0b57 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 16:37:50 +0100 Subject: [PATCH 027/173] Linting. --- packages/worker/src/api/controllers/global/flags.js | 2 +- packages/worker/src/api/routes/global/auth.js | 5 +---- packages/worker/src/api/routes/global/flags.js | 5 ++--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/worker/src/api/controllers/global/flags.js b/packages/worker/src/api/controllers/global/flags.js index 26e998e66..fdfc49afa 100644 --- a/packages/worker/src/api/controllers/global/flags.js +++ b/packages/worker/src/api/controllers/global/flags.js @@ -4,4 +4,4 @@ exports.fetch = async ctx => { ctx.body = { multiTenancy: !!env.MULTI_TENANCY, } -} \ No newline at end of file +} diff --git a/packages/worker/src/api/routes/global/auth.js b/packages/worker/src/api/routes/global/auth.js index 19a61df2e..1546cb1c0 100644 --- a/packages/worker/src/api/routes/global/auth.js +++ b/packages/worker/src/api/routes/global/auth.js @@ -55,10 +55,7 @@ router // deprecated - used by the default system before tenancy .get("/api/global/auth/google", authController.googlePreAuth) .get("/api/global/auth/google/callback", authController.googleAuth) - .get( - "/api/global/auth/oidc/configs/:configId", - authController.oidcPreAuth - ) + .get("/api/global/auth/oidc/configs/:configId", authController.oidcPreAuth) .get("/api/global/auth/oidc/callback", authController.oidcAuth) module.exports = router diff --git a/packages/worker/src/api/routes/global/flags.js b/packages/worker/src/api/routes/global/flags.js index 5f63455db..a1367f4a8 100644 --- a/packages/worker/src/api/routes/global/flags.js +++ b/packages/worker/src/api/routes/global/flags.js @@ -3,7 +3,6 @@ const controller = require("../../controllers/global/flags") const router = Router() -router - .get("/api/global/flags", controller.fetch) +router.get("/api/global/flags", controller.fetch) -module.exports = router \ No newline at end of file +module.exports = router From 155e30965f5dacf92b8325f20ebe05e5a2cb0e9e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 22:36:16 +0100 Subject: [PATCH 028/173] Fixing test cases and bugs that they raised. --- packages/auth/src/db/utils.js | 14 +++++++++++--- .../routes/tests/utilities/TestFunctions.js | 5 +++-- .../src/tests/utilities/TestConfiguration.js | 13 +++++++++---- .../server/src/tests/utilities/structures.js | 2 ++ packages/worker/scripts/jestSetup.js | 1 + .../worker/src/api/controllers/global/auth.js | 3 ++- .../src/api/controllers/global/templates.js | 10 +++++----- .../worker/src/api/controllers/global/users.js | 9 ++++++++- .../worker/src/api/routes/tests/auth.spec.js | 4 ++-- .../worker/src/api/routes/tests/email.spec.js | 2 ++ .../worker/src/api/routes/tests/users.spec.js | 3 ++- .../tests/utilities/TestConfiguration.js | 11 +++++++---- .../api/routes/tests/utilities/structures.js | 1 + .../worker/src/constants/templates/index.js | 18 +++++++++++++----- packages/worker/src/utilities/email.js | 11 ++++++----- packages/worker/src/utilities/templates.js | 16 ++++++++++++---- 16 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 packages/worker/src/api/routes/tests/utilities/structures.js diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 501df2de2..57772a828 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -82,13 +82,21 @@ exports.getGlobalDB = tenantId => { } /** - * Given a koa context this tries to find the correct tenant Global DB. + * Given a koa context this tries to extra what tenant is being accessed. */ -exports.getGlobalDBFromCtx = ctx => { +exports.getTenantIdFromCtx = ctx => { const user = ctx.user || {} const params = ctx.request.params || {} const query = ctx.request.query || {} - return exports.getGlobalDB(user.tenantId || params.tenantId || query.tenantId) + return user.tenantId || params.tenantId || query.tenantId +} + +/** + * Given a koa context this tries to find the correct tenant Global DB. + */ +exports.getGlobalDBFromCtx = ctx => { + const tenantId = exports.getTenantIdFromCtx(ctx) + return exports.getGlobalDB(tenantId) } /** diff --git a/packages/server/src/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js index 3ba5b4d69..944d2ac52 100644 --- a/packages/server/src/api/routes/tests/utilities/TestFunctions.js +++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js @@ -3,6 +3,7 @@ const appController = require("../../../controllers/application") const CouchDB = require("../../../../db") const { AppStatus } = require("../../../../db/utils") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") +const { TENANT_ID } = require("../../../../tests/utilities/structures") function Request(appId, params) { this.appId = appId @@ -16,8 +17,8 @@ exports.getAllTableRows = async config => { return req.body } -exports.clearAllApps = async () => { - const req = { query: { status: AppStatus.DEV } } +exports.clearAllApps = async (tenantId = TENANT_ID) => { + const req = { query: { status: AppStatus.DEV }, user: { tenantId } } await appController.fetch(req) const apps = req.body if (!apps || apps.length <= 0) { diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index ceef2a3e9..da7ece2e8 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -10,20 +10,23 @@ const { basicScreen, basicLayout, basicWebhook, + TENANT_ID, } = require("./structures") const controllers = require("./controllers") const supertest = require("supertest") const { cleanup } = require("../../utilities/fileSystem") const { Cookies } = require("@budibase/auth").constants const { jwt } = require("@budibase/auth").auth +const auth = require("@budibase/auth") const { getGlobalDB } = require("@budibase/auth/db") const { createASession } = require("@budibase/auth/sessions") const { user: userCache } = require("@budibase/auth/cache") +const CouchDB = require("../../db") +auth.init(CouchDB) const GLOBAL_USER_ID = "us_uuid1" const EMAIL = "babs@babs.com" const PASSWORD = "babs_password" -const TENANT_ID = "default" class TestConfiguration { constructor(openServer = true) { @@ -52,7 +55,7 @@ class TestConfiguration { request.cookies = { set: () => {}, get: () => {} } request.config = { jwtSecret: env.JWT_SECRET } request.appId = this.appId - request.user = { appId: this.appId } + request.user = { appId: this.appId, tenantId: TENANT_ID } request.query = {} request.request = { body: config, @@ -78,7 +81,7 @@ class TestConfiguration { roles: roles || {}, tenantId: TENANT_ID, } - await createASession(id, "sessionid") + await createASession(id, { sessionId: "sessionid", tenantId: TENANT_ID }) if (builder) { user.builder = { global: true } } @@ -108,6 +111,7 @@ class TestConfiguration { const auth = { userId: GLOBAL_USER_ID, sessionId: "sessionid", + tenantId: TENANT_ID, } const app = { roleId: BUILTIN_ROLE_IDS.ADMIN, @@ -334,11 +338,12 @@ class TestConfiguration { if (!email || !password) { await this.createUser() } - await createASession(userId, "sessionid") + await createASession(userId, { sessionId: "sessionid", tenantId: TENANT_ID }) // have to fake this const auth = { userId, sessionId: "sessionid", + tenantId: TENANT_ID, } const app = { roleId: roleId, diff --git a/packages/server/src/tests/utilities/structures.js b/packages/server/src/tests/utilities/structures.js index 91996a780..e4b2c7e1f 100644 --- a/packages/server/src/tests/utilities/structures.js +++ b/packages/server/src/tests/utilities/structures.js @@ -4,6 +4,8 @@ const { createHomeScreen } = require("../../constants/screens") const { EMPTY_LAYOUT } = require("../../constants/layouts") const { cloneDeep } = require("lodash/fp") +exports.TENANT_ID = "default" + exports.basicTable = () => { return { name: "TestTable", diff --git a/packages/worker/scripts/jestSetup.js b/packages/worker/scripts/jestSetup.js index 07648f693..374edfb94 100644 --- a/packages/worker/scripts/jestSetup.js +++ b/packages/worker/scripts/jestSetup.js @@ -3,3 +3,4 @@ const env = require("../src/environment") env._set("NODE_ENV", "jest") env._set("JWT_SECRET", "test-jwtsecret") env._set("LOG_LEVEL", "silent") +env._set("MULTI_TENANCY", true) diff --git a/packages/worker/src/api/controllers/global/auth.js b/packages/worker/src/api/controllers/global/auth.js index c3bbaf41a..f576e697e 100644 --- a/packages/worker/src/api/controllers/global/auth.js +++ b/packages/worker/src/api/controllers/global/auth.js @@ -74,6 +74,7 @@ exports.reset = async ctx => { }) } } catch (err) { + console.log(err) // don't throw any kind of error to the user, this might give away something } ctx.body = { @@ -88,7 +89,7 @@ exports.resetUpdate = async ctx => { const { resetCode, password } = ctx.request.body try { const userId = await checkResetPasswordCode(resetCode) - const db = new getGlobalDB(ctx.params.tenantId) + const db = getGlobalDB(ctx.params.tenantId) const user = await db.get(userId) user.password = await hash(password) await db.put(user) diff --git a/packages/worker/src/api/controllers/global/templates.js b/packages/worker/src/api/controllers/global/templates.js index e781cf5a8..cf9e988b6 100644 --- a/packages/worker/src/api/controllers/global/templates.js +++ b/packages/worker/src/api/controllers/global/templates.js @@ -4,7 +4,7 @@ const { TemplateBindings, GLOBAL_OWNER, } = require("../../../constants") -const { getTemplates } = require("../../../constants/templates") +const { getTemplatesCtx } = require("../../../constants/templates") exports.save = async ctx => { const db = getGlobalDBFromCtx(ctx) @@ -45,23 +45,23 @@ exports.definitions = async ctx => { } exports.fetch = async ctx => { - ctx.body = await getTemplates(ctx) + ctx.body = await getTemplatesCtx(ctx) } exports.fetchByType = async ctx => { - ctx.body = await getTemplates(ctx, { + ctx.body = await getTemplatesCtx(ctx, { type: ctx.params.type, }) } exports.fetchByOwner = async ctx => { - ctx.body = await getTemplates(ctx, { + ctx.body = await getTemplatesCtx(ctx, { ownerId: ctx.params.ownerId, }) } exports.find = async ctx => { - ctx.body = await getTemplates(ctx, { + ctx.body = await getTemplatesCtx(ctx, { id: ctx.params.id, }) } diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index a9d761bce..e0d1e6f10 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -265,12 +265,16 @@ exports.find = async ctx => { } exports.invite = async ctx => { - const { email, userInfo } = ctx.request.body + let { email, userInfo } = ctx.request.body const tenantId = ctx.user.tenantId const existing = await getGlobalUserByEmail(email, tenantId) if (existing) { ctx.throw(400, "Email address already in use.") } + if (!userInfo) { + userInfo = {} + } + userInfo.tenantId = tenantId await sendEmail(tenantId, email, EmailTemplatePurpose.INVITATION, { subject: "{{ company }} platform invitation", info: userInfo, @@ -293,6 +297,9 @@ exports.inviteAccept = async ctx => { email, ...info, } + ctx.user = { + tenantId: info.tenantId, + } // this will flesh out the body response await exports.save(ctx) } catch (err) { diff --git a/packages/worker/src/api/routes/tests/auth.spec.js b/packages/worker/src/api/routes/tests/auth.spec.js index f55e7ac8b..dacff30ce 100644 --- a/packages/worker/src/api/routes/tests/auth.spec.js +++ b/packages/worker/src/api/routes/tests/auth.spec.js @@ -36,8 +36,8 @@ describe("/api/global/auth", () => { expect(sendMailMock).toHaveBeenCalled() const emailCall = sendMailMock.mock.calls[0][0] // after this URL there should be a code - const parts = emailCall.html.split(`http://localhost:10000/builder/auth/${TENANT_ID}/reset?code=`) - code = parts[1].split("\"")[0] + const parts = emailCall.html.split(`http://localhost:10000/builder/auth/reset?code=`) + code = parts[1].split("\"")[0].split("&")[0] expect(code).toBeDefined() }) diff --git a/packages/worker/src/api/routes/tests/email.spec.js b/packages/worker/src/api/routes/tests/email.spec.js index 027ad83fc..c8c93658f 100644 --- a/packages/worker/src/api/routes/tests/email.spec.js +++ b/packages/worker/src/api/routes/tests/email.spec.js @@ -1,5 +1,6 @@ const setup = require("./utilities") const { EmailTemplatePurpose } = require("../../../constants") +const { TENANT_ID } = require("./utilities/structures") // mock the email system const sendMailMock = jest.fn() @@ -29,6 +30,7 @@ describe("/api/global/email", () => { .send({ email: "test@test.com", purpose: EmailTemplatePurpose.INVITATION, + tenantId: TENANT_ID, }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) diff --git a/packages/worker/src/api/routes/tests/users.spec.js b/packages/worker/src/api/routes/tests/users.spec.js index b75364180..f03f9e60b 100644 --- a/packages/worker/src/api/routes/tests/users.spec.js +++ b/packages/worker/src/api/routes/tests/users.spec.js @@ -1,4 +1,5 @@ const setup = require("./utilities") +const { TENANT_ID } = require("./utilities/structures") jest.mock("nodemailer") const sendMailMock = setup.emailMock() @@ -31,7 +32,7 @@ describe("/api/global/users", () => { const emailCall = sendMailMock.mock.calls[0][0] // after this URL there should be a code const parts = emailCall.html.split("http://localhost:10000/builder/invite?code=") - code = parts[1].split("\"")[0] + code = parts[1].split("\"")[0].split("&")[0] expect(code).toBeDefined() }) diff --git a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js index 224b61cf0..fe1bb6864 100644 --- a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js @@ -7,8 +7,10 @@ const { Configs, LOGO_URL } = require("../../../../constants") const { getGlobalUserByEmail } = require("@budibase/auth").utils const { createASession } = require("@budibase/auth/sessions") const { newid } = require("../../../../../../auth/src/hashing") - -const TENANT_ID = "default" +const { TENANT_ID } = require("./structures") +const auth = require("@budibase/auth") +const CouchDB = require("../../../../db") +auth.init(CouchDB) class TestConfiguration { constructor(openServer = true) { @@ -30,7 +32,7 @@ class TestConfiguration { request.cookies = { set: () => {}, get: () => {} } request.config = { jwtSecret: env.JWT_SECRET } request.appId = this.appId - request.user = { appId: this.appId } + request.user = { appId: this.appId, tenantId: TENANT_ID } request.query = {} request.request = { body: config, @@ -60,7 +62,7 @@ class TestConfiguration { null, controllers.users.save ) - await createASession("us_uuid1", "sessionid") + await createASession("us_uuid1", { sessionId: "sessionid", tenantId: TENANT_ID }) } } @@ -233,6 +235,7 @@ class TestConfiguration { { email: "testuser@test.com", password: "test@test.com", + tenantId: TENANT_ID, }, null, controllers.users.adminUser diff --git a/packages/worker/src/api/routes/tests/utilities/structures.js b/packages/worker/src/api/routes/tests/utilities/structures.js new file mode 100644 index 000000000..16701ac3d --- /dev/null +++ b/packages/worker/src/api/routes/tests/utilities/structures.js @@ -0,0 +1 @@ +exports.TENANT_ID = "default" diff --git a/packages/worker/src/constants/templates/index.js b/packages/worker/src/constants/templates/index.js index 026ebf6b9..f9a7257eb 100644 --- a/packages/worker/src/constants/templates/index.js +++ b/packages/worker/src/constants/templates/index.js @@ -6,7 +6,7 @@ const { GLOBAL_OWNER, } = require("../index") const { join } = require("path") -const { getTemplateParams, getGlobalDBFromCtx } = require("@budibase/auth/db") +const { getTemplateParams, getTenantIdFromCtx, getGlobalDB } = require("@budibase/auth/db") exports.EmailTemplates = { [EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile( @@ -48,8 +48,13 @@ exports.addBaseTemplates = (templates, type = null) => { return templates } -exports.getTemplates = async (ctx, { ownerId, type, id } = {}) => { - const db = getGlobalDBFromCtx(ctx) +exports.getTemplatesCtx = async (ctx, opts = {}) => { + const tenantId = getTenantIdFromCtx(ctx) + return exports.getTemplates(tenantId, opts) +} + +exports.getTemplates = async (tenantId, { ownerId, type, id} = {}) => { + const db = getGlobalDB(tenantId) const response = await db.allDocs( getTemplateParams(ownerId || GLOBAL_OWNER, id, { include_docs: true, @@ -66,7 +71,10 @@ exports.getTemplates = async (ctx, { ownerId, type, id } = {}) => { return exports.addBaseTemplates(templates, type) } -exports.getTemplateByPurpose = async (ctx, type, purpose) => { - const templates = await exports.getTemplates(ctx, { type }) +exports.getTemplateByPurpose = async ({ tenantId, ctx }, type, purpose) => { + if (!tenantId && ctx) { + tenantId = getTenantIdFromCtx(ctx) + } + const templates = await exports.getTemplates(tenantId, { type }) return templates.find(template => template.purpose === purpose) } diff --git a/packages/worker/src/utilities/email.js b/packages/worker/src/utilities/email.js index 38fafd101..e3e558369 100644 --- a/packages/worker/src/utilities/email.js +++ b/packages/worker/src/utilities/email.js @@ -60,6 +60,7 @@ async function getLinkCode(purpose, email, user, info = null) { /** * Builds an email using handlebars and the templates found in the system (default or otherwise). + * @param {string} tenantId the ID of the tenant which is sending the email. * @param {string} purpose the purpose of the email being built, e.g. invitation, password reset. * @param {string} email the address which it is being sent to for contextual purposes. * @param {object} context the context which is being used for building the email (hbs context). @@ -67,14 +68,14 @@ async function getLinkCode(purpose, email, user, info = null) { * @param {string|null} contents if using a custom template can supply contents for context. * @return {Promise} returns the built email HTML if all provided parameters were valid. */ -async function buildEmail(purpose, email, context, { user, contents } = {}) { +async function buildEmail(tenantId, purpose, email, context, { user, contents } = {}) { // this isn't a full email if (FULL_EMAIL_PURPOSES.indexOf(purpose) === -1) { throw `Unable to build an email of type ${purpose}` } let [base, body] = await Promise.all([ - getTemplateByPurpose(TYPE, EmailTemplatePurpose.BASE), - getTemplateByPurpose(TYPE, purpose), + getTemplateByPurpose({ tenantId }, TYPE, EmailTemplatePurpose.BASE), + getTemplateByPurpose({ tenantId }, TYPE, purpose), ]) if (!base || !body) { throw "Unable to build email, missing base components" @@ -147,7 +148,7 @@ exports.sendEmail = async ( purpose, { workspaceId, user, from, contents, subject, info } = {} ) => { - const db = new getGlobalDB(tenantId) + const db = getGlobalDB(tenantId) let config = (await getSmtpConfiguration(db, workspaceId)) || {} if (Object.keys(config).length === 0 && !TEST_MODE) { throw "Unable to find SMTP configuration." @@ -159,7 +160,7 @@ exports.sendEmail = async ( const message = { from: from || config.from, to: email, - html: await buildEmail(purpose, email, context, { user, contents }), + html: await buildEmail(tenantId, purpose, email, context, { user, contents }), } if (subject || config.subject) { message.subject = await processString(subject || config.subject, context) diff --git a/packages/worker/src/utilities/templates.js b/packages/worker/src/utilities/templates.js index dfd139fb8..40fc4e227 100644 --- a/packages/worker/src/utilities/templates.js +++ b/packages/worker/src/utilities/templates.js @@ -11,8 +11,16 @@ const env = require("../environment") const LOCAL_URL = `http://localhost:${env.CLUSTER_PORT || 10000}` const BASE_COMPANY = "Budibase" +function addTenantToUrl(url, tenantId) { + if (env.MULTI_TENANCY) { + const char = url.indexOf("?") === -1 ? "?" : "&" + url += `${char}tenantId=${tenantId}` + } + return url +} + exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => { - const db = new getGlobalDB(tenantId) + const db = getGlobalDB(tenantId) // TODO: use more granular settings in the future if required let settings = (await getScopedConfig(db, { type: Configs.SETTINGS })) || {} if (!settings || !settings.platformUrl) { @@ -26,7 +34,7 @@ exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => { [InternalTemplateBindings.COMPANY]: settings.company || BASE_COMPANY, [InternalTemplateBindings.DOCS_URL]: settings.docsUrl || "https://docs.budibase.com/", - [InternalTemplateBindings.LOGIN_URL]: checkSlashesInUrl(`${URL}/login`), + [InternalTemplateBindings.LOGIN_URL]: checkSlashesInUrl(addTenantToUrl(`${URL}/login`, tenantId)), [InternalTemplateBindings.CURRENT_DATE]: new Date().toISOString(), [InternalTemplateBindings.CURRENT_YEAR]: new Date().getFullYear(), } @@ -35,13 +43,13 @@ exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => { case EmailTemplatePurpose.PASSWORD_RECOVERY: context[InternalTemplateBindings.RESET_CODE] = code context[InternalTemplateBindings.RESET_URL] = checkSlashesInUrl( - `${URL}/builder/auth/reset?code=${code}` + addTenantToUrl(`${URL}/builder/auth/reset?code=${code}`, tenantId) ) break case EmailTemplatePurpose.INVITATION: context[InternalTemplateBindings.INVITE_CODE] = code context[InternalTemplateBindings.INVITE_URL] = checkSlashesInUrl( - `${URL}/builder/invite?code=${code}` + addTenantToUrl(`${URL}/builder/invite?code=${code}&tenantId=${tenantId}`, tenantId) ) break } From 1153001af84028731a9c5e3e3fc8a285dfb5c94e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 22:37:20 +0100 Subject: [PATCH 029/173] Linting. --- .../server/src/tests/utilities/TestConfiguration.js | 5 ++++- .../api/routes/tests/utilities/TestConfiguration.js | 5 ++++- packages/worker/src/constants/templates/index.js | 8 ++++++-- packages/worker/src/utilities/email.js | 13 +++++++++++-- packages/worker/src/utilities/templates.js | 9 +++++++-- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index da7ece2e8..1e418ed4a 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -338,7 +338,10 @@ class TestConfiguration { if (!email || !password) { await this.createUser() } - await createASession(userId, { sessionId: "sessionid", tenantId: TENANT_ID }) + await createASession(userId, { + sessionId: "sessionid", + tenantId: TENANT_ID, + }) // have to fake this const auth = { userId, diff --git a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js index fe1bb6864..26023b02f 100644 --- a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js @@ -62,7 +62,10 @@ class TestConfiguration { null, controllers.users.save ) - await createASession("us_uuid1", { sessionId: "sessionid", tenantId: TENANT_ID }) + await createASession("us_uuid1", { + sessionId: "sessionid", + tenantId: TENANT_ID, + }) } } diff --git a/packages/worker/src/constants/templates/index.js b/packages/worker/src/constants/templates/index.js index f9a7257eb..805e4b79b 100644 --- a/packages/worker/src/constants/templates/index.js +++ b/packages/worker/src/constants/templates/index.js @@ -6,7 +6,11 @@ const { GLOBAL_OWNER, } = require("../index") const { join } = require("path") -const { getTemplateParams, getTenantIdFromCtx, getGlobalDB } = require("@budibase/auth/db") +const { + getTemplateParams, + getTenantIdFromCtx, + getGlobalDB, +} = require("@budibase/auth/db") exports.EmailTemplates = { [EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile( @@ -53,7 +57,7 @@ exports.getTemplatesCtx = async (ctx, opts = {}) => { return exports.getTemplates(tenantId, opts) } -exports.getTemplates = async (tenantId, { ownerId, type, id} = {}) => { +exports.getTemplates = async (tenantId, { ownerId, type, id } = {}) => { const db = getGlobalDB(tenantId) const response = await db.allDocs( getTemplateParams(ownerId || GLOBAL_OWNER, id, { diff --git a/packages/worker/src/utilities/email.js b/packages/worker/src/utilities/email.js index e3e558369..eec2743b6 100644 --- a/packages/worker/src/utilities/email.js +++ b/packages/worker/src/utilities/email.js @@ -68,7 +68,13 @@ async function getLinkCode(purpose, email, user, info = null) { * @param {string|null} contents if using a custom template can supply contents for context. * @return {Promise} returns the built email HTML if all provided parameters were valid. */ -async function buildEmail(tenantId, purpose, email, context, { user, contents } = {}) { +async function buildEmail( + tenantId, + purpose, + email, + context, + { user, contents } = {} +) { // this isn't a full email if (FULL_EMAIL_PURPOSES.indexOf(purpose) === -1) { throw `Unable to build an email of type ${purpose}` @@ -160,7 +166,10 @@ exports.sendEmail = async ( const message = { from: from || config.from, to: email, - html: await buildEmail(tenantId, purpose, email, context, { user, contents }), + html: await buildEmail(tenantId, purpose, email, context, { + user, + contents, + }), } if (subject || config.subject) { message.subject = await processString(subject || config.subject, context) diff --git a/packages/worker/src/utilities/templates.js b/packages/worker/src/utilities/templates.js index 40fc4e227..51628064b 100644 --- a/packages/worker/src/utilities/templates.js +++ b/packages/worker/src/utilities/templates.js @@ -34,7 +34,9 @@ exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => { [InternalTemplateBindings.COMPANY]: settings.company || BASE_COMPANY, [InternalTemplateBindings.DOCS_URL]: settings.docsUrl || "https://docs.budibase.com/", - [InternalTemplateBindings.LOGIN_URL]: checkSlashesInUrl(addTenantToUrl(`${URL}/login`, tenantId)), + [InternalTemplateBindings.LOGIN_URL]: checkSlashesInUrl( + addTenantToUrl(`${URL}/login`, tenantId) + ), [InternalTemplateBindings.CURRENT_DATE]: new Date().toISOString(), [InternalTemplateBindings.CURRENT_YEAR]: new Date().getFullYear(), } @@ -49,7 +51,10 @@ exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => { case EmailTemplatePurpose.INVITATION: context[InternalTemplateBindings.INVITE_CODE] = code context[InternalTemplateBindings.INVITE_URL] = checkSlashesInUrl( - addTenantToUrl(`${URL}/builder/invite?code=${code}&tenantId=${tenantId}`, tenantId) + addTenantToUrl( + `${URL}/builder/invite?code=${code}&tenantId=${tenantId}`, + tenantId + ) ) break } From 68a2f9835e97f212c25be4c7ffcea57cfe7282f7 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 16:53:22 +0100 Subject: [PATCH 030/173] Adjusting how redis modules are loaded, making sure client is always set, before init. --- packages/server/src/utilities/redis.js | 6 ++++-- packages/worker/src/utilities/redis.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/server/src/utilities/redis.js b/packages/server/src/utilities/redis.js index e1fa63200..6f1cf4660 100644 --- a/packages/server/src/utilities/redis.js +++ b/packages/server/src/utilities/redis.js @@ -7,8 +7,10 @@ let devAppClient, debounceClient // we init this as we want to keep the connection open all the time // reduces the performance hit exports.init = async () => { - devAppClient = await new Client(utils.Databases.DEV_LOCKS).init() - debounceClient = await new Client(utils.Databases.DEBOUNCE).init() + devAppClient = new Client(utils.Databases.DEV_LOCKS) + debounceClient = new Client(utils.Databases.DEBOUNCE) + await devAppClient.init() + await debounceClient.init() } exports.shutdown = async () => { diff --git a/packages/worker/src/utilities/redis.js b/packages/worker/src/utilities/redis.js index 6e55795de..6dd4491bc 100644 --- a/packages/worker/src/utilities/redis.js +++ b/packages/worker/src/utilities/redis.js @@ -43,8 +43,10 @@ async function getACode(db, code, deleteCode = true) { } exports.init = async () => { - pwResetClient = await new Client(utils.Databases.PW_RESETS).init() - invitationClient = await new Client(utils.Databases.INVITATIONS).init() + pwResetClient = new Client(utils.Databases.PW_RESETS) + invitationClient = new Client(utils.Databases.INVITATIONS) + await pwResetClient.init() + await invitationClient.init() } /** From f86aa0ac78561b50ed88ff3497771f1414f77073 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 17:26:54 +0100 Subject: [PATCH 031/173] Removing accidental symbols. --- packages/builder/src/pages/builder/index.svelte | 2 +- packages/worker/src/api/controllers/global/configs.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/pages/builder/index.svelte b/packages/builder/src/pages/builder/index.svelte index 963870716..fba581a04 100644 --- a/packages/builder/src/pages/builder/index.svelte +++ b/packages/builder/src/pages/builder/index.svelte @@ -8,7 +8,7 @@ if (!$auth.user) { $redirect(`./auth`) } else if ($auth.user.builder?.global) { - $redirect(`./portal}`) + $redirect(`./portal`) } else { $redirect(`./apps`) } diff --git a/packages/worker/src/api/controllers/global/configs.js b/packages/worker/src/api/controllers/global/configs.js index 7cd03c728..b48de48fe 100644 --- a/packages/worker/src/api/controllers/global/configs.js +++ b/packages/worker/src/api/controllers/global/configs.js @@ -148,8 +148,8 @@ exports.publicSettings = async function (ctx) { // google button flag if (googleConfig && googleConfig.config) { // activated by default for configs pre-activated flag - config.config.google = googleConfig.config.activated == null || - googleConfig.config.activated + config.config.google = + googleConfig.config.activated == null || googleConfig.config.activated } else { config.config.google = false } From 93f5419072c512186ab2392d69bd440bea3facf1 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 17:41:00 +0100 Subject: [PATCH 032/173] Adding use of the tenancy ID. --- packages/auth/src/middleware/authenticated.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index e8fbb56fb..c79725233 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -75,11 +75,15 @@ module.exports = (noAuthPatterns = [], opts) => { } } const apiKey = ctx.request.headers[Headers.API_KEY] + const tenantId = ctx.request.headers[Headers.TENANT_ID] // this is an internal request, no user made it if (!authenticated && apiKey && apiKey === env.INTERNAL_API_KEY) { authenticated = true internal = true } + if (!user && tenantId) { + user = { tenantId } + } // be explicit if (authenticated !== true) { authenticated = false From 19ad28a4910cfd8d4017dd2e4135bc25819df284 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 19:22:04 +0100 Subject: [PATCH 033/173] Adding mechanism for lookup of userId/email to tenantId. --- .../src/api/controllers/global/users.js | 67 ++++++++++++++++--- .../worker/src/api/routes/global/users.js | 1 + 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index e0d1e6f10..9ec292254 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -7,33 +7,55 @@ 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 PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants -async function tryAddTenant(tenantId) { + +async function tryAddTenant(tenantId, userId, email) { const db = new CouchDB(PLATFORM_INFO_DB) - let tenants - try { - tenants = await db.get(TENANT_DOC) - } catch (err) { - // if theres an error don't worry, we'll just write it in + const getDoc = async id => { + if (!id) { + return null + } + try { + return await db.get(id) + } catch (err) { + return { _id: id } + } } - if (!tenants || !Array.isArray(tenants.tenantIds)) { + let [tenants, userIdDoc, emailDoc] = await Promise.all([ + getDoc(TENANT_DOC), + getDoc(userId), + getDoc(email), + ]) + if (!Array.isArray(tenants.tenantIds)) { tenants = { _id: TENANT_DOC, tenantIds: [], } } + let promises = [] + if (userIdDoc) { + userIdDoc.tenantId = tenantId + promises.push(db.put(userIdDoc)) + } + if (emailDoc) { + emailDoc.tenantId = tenantId + promises.push(db.put(emailDoc)) + } if (tenants.tenantIds.indexOf(tenantId) === -1) { tenants.tenantIds.push(tenantId) - await db.put(tenants) + promises.push(db.put(tenants)) } + await Promise.all(promises) } async function doesTenantExist(tenantId) { @@ -67,8 +89,7 @@ async function saveUser(user, tenantId) { throw "No tenancy specified." } const db = getGlobalDB(tenantId) - await tryAddTenant(tenantId) - const { email, password, _id } = user + let { email, password, _id } = user // make sure another user isn't using the same email let dbUser if (email) { @@ -90,10 +111,11 @@ async function saveUser(user, tenantId) { throw "Password must be specified." } + _id = _id || generateGlobalUserID() user = { ...dbUser, ...user, - _id: _id || generateGlobalUserID(), + _id, password: hashedPassword, tenantId, } @@ -110,6 +132,7 @@ async function saveUser(user, tenantId) { password: hashedPassword, ...user, }) + await tryAddTenant(tenantId, _id, email) await userCache.invalidateUser(response.id) return { _id: response.id, @@ -264,6 +287,28 @@ exports.find = async ctx => { ctx.body = user } +exports.tenantLookup = 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 + } + } catch (err) { + if (!env.MULTI_TENANCY) { + tenantId = DEFAULT_TENANT_ID + } else { + ctx.throw(400, "No tenant found.") + } + } + ctx.body = { + tenantId, + } +} + exports.invite = async ctx => { let { email, userInfo } = ctx.request.body const tenantId = ctx.user.tenantId diff --git a/packages/worker/src/api/routes/global/users.js b/packages/worker/src/api/routes/global/users.js index 8be81c493..835983595 100644 --- a/packages/worker/src/api/routes/global/users.js +++ b/packages/worker/src/api/routes/global/users.js @@ -94,6 +94,7 @@ router controller.adminUser ) .get("/api/global/users/self", controller.getSelf) + .get("/api/global/users/tenant/:id", adminOnly, controller.tenantLookup) // global endpoint but needs to come at end (blocks other endpoints otherwise) .get("/api/global/users/:id", adminOnly, controller.find) From a384cc059d9e567b61cca2a9cc1a5ba8ebb16dde Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 20:01:53 +0100 Subject: [PATCH 034/173] Changing how apps are retrieved to be more efficient. --- packages/auth/src/db/utils.js | 33 +++++++++++++------ packages/server/scripts/dev/manage.js | 1 + .../server/src/api/controllers/application.js | 6 ++-- packages/server/src/db/utils.js | 12 ++++--- packages/server/src/environment.js | 1 + packages/worker/scripts/dev/manage.js | 2 +- .../src/api/controllers/global/users.js | 2 +- 7 files changed, 38 insertions(+), 19 deletions(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 57772a828..602520ef1 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -26,19 +26,24 @@ exports.StaticDatabases = { }, } +const PRE_APP = "app" +const PRE_DEV = "dev" + const DocumentTypes = { USER: "us", WORKSPACE: "workspace", CONFIG: "config", TEMPLATE: "template", - APP: "app", - APP_DEV: "app_dev", - APP_METADATA: "app_metadata", + 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 @@ -85,6 +90,9 @@ exports.getGlobalDB = tenantId => { * Given a koa context this tries to extra what tenant is being accessed. */ exports.getTenantIdFromCtx = ctx => { + if (!ctx) { + return null + } const user = ctx.user || {} const params = ctx.request.params || {} const query = ctx.request.query || {} @@ -208,9 +216,18 @@ exports.getAllApps = async ({ tenantId, dev, all } = {}) => { } const CouchDB = getCouch() let allDbs = await CouchDB.allDbs() - const appDbNames = allDbs.filter(dbName => - dbName.startsWith(exports.APP_PREFIX) - ) + const appDbNames = allDbs.filter(dbName => { + const split = dbName.split(SEPARATOR) + // it is an app, check the tenantId + if (split[0] === DocumentTypes.APP) { + const noTenantId = split.length === 2 || split[1] === DocumentTypes.DEV + // tenantId is always right before the UUID + const possibleTenantId = split[split.length - 2] + return (tenantId === DEFAULT_TENANT_ID && noTenantId) || + (possibleTenantId === tenantId) + } + return false + }) const appPromises = appDbNames.map(db => // skip setup otherwise databases could be re-created new CouchDB(db, { skip_setup: true }).get(DocumentTypes.APP_METADATA) @@ -222,10 +239,6 @@ exports.getAllApps = async ({ tenantId, dev, all } = {}) => { const apps = response .filter(result => result.status === "fulfilled") .map(({ value }) => value) - .filter(app => { - const appTenant = !app.tenantId ? DEFAULT_TENANT_ID : app.tenantId - return tenantId === appTenant - }) if (!all) { return apps.filter(app => { if (dev) { diff --git a/packages/server/scripts/dev/manage.js b/packages/server/scripts/dev/manage.js index ffd8c6b9e..96c267431 100644 --- a/packages/server/scripts/dev/manage.js +++ b/packages/server/scripts/dev/manage.js @@ -47,6 +47,7 @@ async function init() { COUCH_DB_PASSWORD: "budibase", COUCH_DB_USER: "budibase", SELF_HOSTED: 1, + MULTI_TENANCY: 0, } let envFile = "" Object.keys(envFileJson).forEach(key => { diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index e91ac08d6..4e13851cf 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -92,8 +92,8 @@ async function getAppUrlIfNotInUse(ctx) { return url } -async function createInstance(template) { - const baseAppId = generateAppID() +async function createInstance(tenantId, template) { + const baseAppId = generateAppID(env.MULTI_TENANCY ? tenantId : null) const appId = generateDevAppID(baseAppId) const db = new CouchDB(appId) @@ -198,7 +198,7 @@ exports.create = async function (ctx) { if (ctx.request.files && ctx.request.files.templateFile) { instanceConfig.file = ctx.request.files.templateFile } - const instance = await createInstance(instanceConfig) + const instance = await createInstance(tenantId, instanceConfig) const appId = instance._id const url = await getAppUrlIfNotInUse(ctx) diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index eb8c32bb5..92734c5e7 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -225,8 +225,12 @@ exports.getLinkParams = (otherProps = {}) => { * Generates a new app ID. * @returns {string} The new app ID which the app doc can be stored under. */ -exports.generateAppID = () => { - return `${DocumentTypes.APP}${SEPARATOR}${newid()}` +exports.generateAppID = (tenantId = null) => { + let id = `${DocumentTypes.APP}${SEPARATOR}` + if (tenantId) { + id += `${tenantId}${SEPARATOR}` + } + return `${id}${newid()}` } /** @@ -235,8 +239,8 @@ exports.generateAppID = () => { */ exports.generateDevAppID = appId => { const prefix = `${DocumentTypes.APP}${SEPARATOR}` - const uuid = appId.split(prefix)[1] - return `${DocumentTypes.APP_DEV}${SEPARATOR}${uuid}` + const rest = appId.split(prefix)[1] + return `${DocumentTypes.APP_DEV}${SEPARATOR}${rest}` } /** diff --git a/packages/server/src/environment.js b/packages/server/src/environment.js index 52c680f65..9f69664ff 100644 --- a/packages/server/src/environment.js +++ b/packages/server/src/environment.js @@ -35,6 +35,7 @@ module.exports = { REDIS_URL: process.env.REDIS_URL, REDIS_PASSWORD: process.env.REDIS_PASSWORD, INTERNAL_API_KEY: process.env.INTERNAL_API_KEY, + MULTI_TENANCY: process.env.MULTI_TENANCY, // environment NODE_ENV: process.env.NODE_ENV, JEST_WORKER_ID: process.env.JEST_WORKER_ID, diff --git a/packages/worker/scripts/dev/manage.js b/packages/worker/scripts/dev/manage.js index a40391b39..dbc2f424d 100644 --- a/packages/worker/scripts/dev/manage.js +++ b/packages/worker/scripts/dev/manage.js @@ -16,7 +16,7 @@ async function init() { REDIS_PASSWORD: "budibase", MINIO_URL: "http://localhost:10000/", COUCH_DB_URL: "http://budibase:budibase@localhost:10000/db/", - MULTI_TENANCY: false, + MULTI_TENANCY: 0, } let envFile = "" Object.keys(envFileJson).forEach(key => { diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index 9ec292254..cb0be9e2a 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -211,7 +211,7 @@ exports.destroy = async ctx => { exports.removeAppRole = async ctx => { const { appId } = ctx.params const db = getGlobalDBFromCtx(ctx) - const users = await allUsers() + const users = await allUsers(ctx) const bulk = [] const cacheInvalidations = [] for (let user of users) { From cfea1846331371a427dc3bb5c3a36a78edd026ca Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 20:03:11 +0100 Subject: [PATCH 035/173] Linting. --- packages/auth/src/db/utils.js | 9 +++++---- packages/worker/src/api/controllers/global/users.js | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 602520ef1..7850a03ff 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -43,8 +43,7 @@ const DocumentTypes = { exports.DocumentTypes = DocumentTypes exports.APP_PREFIX = DocumentTypes.APP + SEPARATOR -exports.APP_DEV = -exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR +exports.APP_DEV = exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR exports.SEPARATOR = SEPARATOR function isDevApp(app) { @@ -223,8 +222,10 @@ exports.getAllApps = async ({ tenantId, dev, all } = {}) => { const noTenantId = split.length === 2 || split[1] === DocumentTypes.DEV // tenantId is always right before the UUID const possibleTenantId = split[split.length - 2] - return (tenantId === DEFAULT_TENANT_ID && noTenantId) || - (possibleTenantId === tenantId) + return ( + (tenantId === DEFAULT_TENANT_ID && noTenantId) || + possibleTenantId === tenantId + ) } return false }) diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index cb0be9e2a..f6bf76c9c 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -18,7 +18,6 @@ const env = require("../../../environment") const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants - async function tryAddTenant(tenantId, userId, email) { const db = new CouchDB(PLATFORM_INFO_DB) const getDoc = async id => { From e76c44000b6a94f370198e67553a07dca933f568 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 20:08:16 +0100 Subject: [PATCH 036/173] Fixing test case. --- .../auth/src/middleware/passport/tests/google.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/auth/src/middleware/passport/tests/google.spec.js b/packages/auth/src/middleware/passport/tests/google.spec.js index e753a23b6..9cc878bba 100644 --- a/packages/auth/src/middleware/passport/tests/google.spec.js +++ b/packages/auth/src/middleware/passport/tests/google.spec.js @@ -5,7 +5,6 @@ const { data } = require("./utilities/mock-data") const TENANT_ID = "default" const googleConfig = { - callbackURL: "http://somecallbackurl", clientID: data.clientID, clientSecret: data.clientSecret, } @@ -28,13 +27,14 @@ describe("google", () => { it("should create successfully create a google strategy", async () => { const google = require("../google") - - await google.strategyFactory(googleConfig, `/api/global/auth/${TENANT_ID}/google/callback`) + + const callbackUrl = `/api/global/auth/${TENANT_ID}/google/callback` + await google.strategyFactory(googleConfig, callbackUrl) const expectedOptions = { clientID: googleConfig.clientID, clientSecret: googleConfig.clientSecret, - callbackURL: googleConfig.callbackURL, + callbackURL: callbackUrl, } expect(mockStrategy).toHaveBeenCalledWith( From 35d5a2d08298005ea60a748dfaaa6f42d05c8fb9 Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Mon, 26 Jul 2021 13:58:18 +0200 Subject: [PATCH 037/173] clear form action - draft --- .../EventsEditor/actions/index.js | 3 ++ packages/client/src/constants.js | 1 + packages/client/src/utils/buttonActions.js | 9 ++++++ .../src/forms/InnerForm.svelte | 28 +++++++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js index b8114461f..2c9023714 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js @@ -42,4 +42,7 @@ export default [ name: "Log Out", component: LogOut, }, + { + name: "Clear Form", + }, ] diff --git a/packages/client/src/constants.js b/packages/client/src/constants.js index e0595c0ce..049b16801 100644 --- a/packages/client/src/constants.js +++ b/packages/client/src/constants.js @@ -6,6 +6,7 @@ export const ActionTypes = { ValidateForm: "ValidateForm", RefreshDatasource: "RefreshDatasource", SetDataProviderQuery: "SetDataProviderQuery", + ClearForm: "ClearForm" } export const ApiVersion = "1" diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index af4826a1c..d7707bfb1 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -77,6 +77,14 @@ const refreshDatasourceHandler = async (action, context) => { ) } +const clearFormHandler = async (action, context) => { + return await executeActionHandler( + context, + action.parameters.componentId, + ActionTypes.ClearForm + ) +} + const handlerMap = { ["Save Row"]: saveRowHandler, ["Delete Row"]: deleteRowHandler, @@ -85,6 +93,7 @@ const handlerMap = { ["Trigger Automation"]: triggerAutomationHandler, ["Validate Form"]: validateFormHandler, ["Refresh Datasource"]: refreshDatasourceHandler, + ["Clear Form"]: clearFormHandler } const confirmTextMap = { diff --git a/packages/standard-components/src/forms/InnerForm.svelte b/packages/standard-components/src/forms/InnerForm.svelte index 3a63b63bd..55e14217e 100644 --- a/packages/standard-components/src/forms/InnerForm.svelte +++ b/packages/standard-components/src/forms/InnerForm.svelte @@ -64,6 +64,13 @@ }) return get(formState).valid }, + clear: () => { + const fields = Object.keys(fieldMap) + fields.forEach(field => { + const { fieldApi } = fieldMap[field] + fieldApi.clearValue(); + }) + } } // Provide both form API and state to children @@ -72,6 +79,7 @@ // Action context to pass to children const actions = [ { type: ActionTypes.ValidateForm, callback: formApi.validate }, + { type: ActionTypes.ClearForm, callback: formApi.clear }, ] // Creates an API for a specific field @@ -108,8 +116,28 @@ return !newError } + + const clearValue = () => { + const { fieldState } = fieldMap[field] + fieldState.update(state => { + state.value = defaultValue + state.error = null + return state + }) + + formState.update(state => { + state.values = { ...state.values, [field]: defaultValue } + delete state.errors[field] + state.valid = Object.keys(state.errors).length === 0 + return state + }) + + return true + } + return { setValue, + clearValue, validate: () => { const { fieldState } = fieldMap[field] setValue(get(fieldState).value, true) From 5c58630ed7c2a036b64ddab9b447349b00e4b3a8 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 13:19:47 +0100 Subject: [PATCH 038/173] Fixing up everything found when testing with Cypress. --- packages/auth/src/db/utils.js | 3 +-- packages/builder/src/pages/builder/auth/org.svelte | 4 ++++ packages/server/scripts/dev/manage.js | 2 +- packages/server/src/api/controllers/application.js | 5 +++-- packages/server/src/api/routes/application.js | 2 +- packages/worker/scripts/dev/manage.js | 3 ++- packages/worker/src/api/controllers/app.js | 3 ++- packages/worker/src/api/controllers/global/configs.js | 3 ++- packages/worker/src/api/controllers/global/roles.js | 2 +- 9 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 7850a03ff..4650b6f12 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -209,11 +209,10 @@ exports.getDeployedAppID = appId => { * different users/companies apps as there is no security around it - all apps are returned. * @return {Promise} returns the app information document stored in each app database. */ -exports.getAllApps = async ({ tenantId, dev, all } = {}) => { +exports.getAllApps = async (CouchDB, { tenantId, dev, all } = {}) => { if (!tenantId) { tenantId = DEFAULT_TENANT_ID } - const CouchDB = getCouch() let allDbs = await CouchDB.allDbs() const appDbNames = allDbs.filter(dbName => { const split = dbName.split(SEPARATOR) diff --git a/packages/builder/src/pages/builder/auth/org.svelte b/packages/builder/src/pages/builder/auth/org.svelte index caf31b265..0e0fd27c7 100644 --- a/packages/builder/src/pages/builder/auth/org.svelte +++ b/packages/builder/src/pages/builder/auth/org.svelte @@ -7,6 +7,7 @@ import { onMount } from "svelte" let tenantId = get(auth).tenantSet ? get(auth).tenantId : "" + $: multiTenancyEnabled = $admin.multiTenancy async function setOrg() { if (tenantId == null || tenantId === "") { @@ -24,6 +25,9 @@ onMount(() => { auth.checkQueryString() + if (!multiTenancyEnabled) { + $goto("../") + } }) diff --git a/packages/server/scripts/dev/manage.js b/packages/server/scripts/dev/manage.js index 96c267431..c24d0057f 100644 --- a/packages/server/scripts/dev/manage.js +++ b/packages/server/scripts/dev/manage.js @@ -47,7 +47,7 @@ async function init() { COUCH_DB_PASSWORD: "budibase", COUCH_DB_USER: "budibase", SELF_HOSTED: 1, - MULTI_TENANCY: 0, + MULTI_TENANCY: "", } let envFile = "" Object.keys(envFileJson).forEach(key => { diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 4e13851cf..15c4c9bf7 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -129,7 +129,7 @@ exports.fetch = async function (ctx) { const dev = ctx.query && ctx.query.status === AppStatus.DEV const all = ctx.query && ctx.query.status === AppStatus.ALL const tenantId = ctx.user.tenantId - const apps = await getAllApps({ tenantId, dev, all }) + const apps = await getAllApps(CouchDB, { tenantId, dev, all }) // get the locks for all the dev apps if (dev || all) { @@ -226,7 +226,8 @@ exports.create = async function (ctx) { updatedAt: new Date().toISOString(), createdAt: new Date().toISOString(), } - await db.put(newApplication, { force: true }) + const response = await db.put(newApplication, { force: true }) + newApplication._rev = response.rev await createEmptyAppPackage(ctx, newApplication) /* istanbul ignore next */ diff --git a/packages/server/src/api/routes/application.js b/packages/server/src/api/routes/application.js index c2eb19e10..c1d39acbd 100644 --- a/packages/server/src/api/routes/application.js +++ b/packages/server/src/api/routes/application.js @@ -6,11 +6,11 @@ const { BUILDER } = require("@budibase/auth/permissions") const router = Router() router + .post("/api/applications", authorized(BUILDER), controller.create) .get("/api/applications/:appId/definition", controller.fetchAppDefinition) .get("/api/applications", controller.fetch) .get("/api/applications/:appId/appPackage", controller.fetchAppPackage) .put("/api/applications/:appId", authorized(BUILDER), controller.update) - .post("/api/applications", authorized(BUILDER), controller.create) .post( "/api/applications/:appId/client/update", authorized(BUILDER), diff --git a/packages/worker/scripts/dev/manage.js b/packages/worker/scripts/dev/manage.js index dbc2f424d..682c28fea 100644 --- a/packages/worker/scripts/dev/manage.js +++ b/packages/worker/scripts/dev/manage.js @@ -16,7 +16,8 @@ async function init() { REDIS_PASSWORD: "budibase", MINIO_URL: "http://localhost:10000/", COUCH_DB_URL: "http://budibase:budibase@localhost:10000/db/", - MULTI_TENANCY: 0, + // empty string is false + MULTI_TENANCY: "", } let envFile = "" Object.keys(envFileJson).forEach(key => { diff --git a/packages/worker/src/api/controllers/app.js b/packages/worker/src/api/controllers/app.js index fc3d3535c..a7b6c5032 100644 --- a/packages/worker/src/api/controllers/app.js +++ b/packages/worker/src/api/controllers/app.js @@ -1,10 +1,11 @@ const { getAllApps } = require("@budibase/auth/db") +const CouchDB = require("../../db") const URL_REGEX_SLASH = /\/|\\/g exports.getApps = async ctx => { const tenantId = ctx.user.tenantId - const apps = await getAllApps({ tenantId }) + const apps = await getAllApps(CouchDB, { tenantId }) const body = {} for (let app of apps) { diff --git a/packages/worker/src/api/controllers/global/configs.js b/packages/worker/src/api/controllers/global/configs.js index b48de48fe..1eb2064b8 100644 --- a/packages/worker/src/api/controllers/global/configs.js +++ b/packages/worker/src/api/controllers/global/configs.js @@ -10,6 +10,7 @@ const { const { Configs } = require("../../../constants") const email = require("../../../utilities/email") const { upload, ObjectStoreBuckets } = require("@budibase/auth").objectStore +const CouchDB = require("../../../db") exports.save = async function (ctx) { const db = getGlobalDBFromCtx(ctx) @@ -224,7 +225,7 @@ exports.configChecklist = async function (ctx) { // TODO: Watch get started video // Apps exist - const apps = await getAllApps({ tenantId }) + const apps = await getAllApps(CouchDB, { tenantId }) // They have set up SMTP const smtpConfig = await getScopedFullConfig(db, { diff --git a/packages/worker/src/api/controllers/global/roles.js b/packages/worker/src/api/controllers/global/roles.js index b00741ead..1aae07241 100644 --- a/packages/worker/src/api/controllers/global/roles.js +++ b/packages/worker/src/api/controllers/global/roles.js @@ -9,7 +9,7 @@ const CouchDB = require("../../../db") exports.fetch = async ctx => { const tenantId = ctx.user.tenantId // always use the dev apps as they'll be most up to date (true) - const apps = await getAllApps({ tenantId, all: true }) + const apps = await getAllApps(CouchDB, { tenantId, all: true }) const promises = [] for (let app of apps) { // use dev app IDs From 669685580f37c8a083aec60068c3b2e5878ecb01 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 13:20:42 +0100 Subject: [PATCH 039/173] Linting. --- packages/auth/src/db/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 4650b6f12..259f24406 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -1,6 +1,6 @@ const { newid } = require("../hashing") const Replication = require("./Replication") -const { getDB, getCouch } = require("./index") +const { getDB } = require("./index") const { DEFAULT_TENANT_ID } = require("../constants") const UNICODE_MAX = "\ufff0" From c7d4c9793a7d92a98d8fad1718c00342c03dcf58 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 13:23:11 +0100 Subject: [PATCH 040/173] Adding in admin API backwards compat. --- packages/worker/src/api/routes/global/auth.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/worker/src/api/routes/global/auth.js b/packages/worker/src/api/routes/global/auth.js index 1546cb1c0..23393a325 100644 --- a/packages/worker/src/api/routes/global/auth.js +++ b/packages/worker/src/api/routes/global/auth.js @@ -53,9 +53,10 @@ router ) .get("/api/global/auth/:tenantId/oidc/callback", authController.oidcAuth) // deprecated - used by the default system before tenancy - .get("/api/global/auth/google", authController.googlePreAuth) + .get("/api/admin/auth/google/callback", authController.googleAuth) .get("/api/global/auth/google/callback", authController.googleAuth) - .get("/api/global/auth/oidc/configs/:configId", authController.oidcPreAuth) + .get("/api/admin/auth/oidc/callback", authController.oidcAuth) .get("/api/global/auth/oidc/callback", authController.oidcAuth) + module.exports = router From 61c8f2935bd0dc8379d4efdabdd29a5f04f8abc0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 13:31:39 +0100 Subject: [PATCH 041/173] Linting. --- packages/worker/src/api/routes/global/auth.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/worker/src/api/routes/global/auth.js b/packages/worker/src/api/routes/global/auth.js index 23393a325..34260dee7 100644 --- a/packages/worker/src/api/routes/global/auth.js +++ b/packages/worker/src/api/routes/global/auth.js @@ -58,5 +58,4 @@ router .get("/api/admin/auth/oidc/callback", authController.oidcAuth) .get("/api/global/auth/oidc/callback", authController.oidcAuth) - module.exports = router From 5343a8b0719d41fbc6f20c04cc7c49d1ef67be6a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 13:40:50 +0100 Subject: [PATCH 042/173] Removing un-necessary log statements. --- .../builder/src/pages/builder/portal/manage/auth/index.svelte | 1 - .../builder/src/pages/builder/portal/manage/email/index.svelte | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte index 7527343ba..458097bdb 100644 --- a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte @@ -267,7 +267,6 @@ config: { configs: [{ activated: true }] }, } } else { - console.log("hello") originalOidcDoc = cloneDeep(oidcDoc) providers.oidc = oidcDoc } diff --git a/packages/builder/src/pages/builder/portal/manage/email/index.svelte b/packages/builder/src/pages/builder/portal/manage/email/index.svelte index 6e40442d4..516162551 100644 --- a/packages/builder/src/pages/builder/portal/manage/email/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/email/index.svelte @@ -92,7 +92,6 @@ } loading = false requireAuth = smtpConfig.config.auth != null - console.log(requireAuth) // always attach the auth for the forms purpose - // this will be removed later if required if (!smtpDoc.config.auth) { From 82cc14ed5332fe891c12831186c2f1098605bad9 Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Mon, 26 Jul 2021 15:05:22 +0200 Subject: [PATCH 043/173] clear form action - adjust manifest and add settings form --- .../EventsEditor/actions/ClearForm.svelte | 35 +++++++++++++++++++ .../EventsEditor/actions/index.js | 2 ++ packages/standard-components/manifest.json | 3 +- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ClearForm.svelte diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ClearForm.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ClearForm.svelte new file mode 100644 index 000000000..74eae6283 --- /dev/null +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ClearForm.svelte @@ -0,0 +1,35 @@ + + +
+ + ($touched.name = true)} + on:change={() => dirty = true} + label="Name" + /> + + diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte index 6c229c198..02260e19e 100644 --- a/packages/builder/src/pages/builder/portal/apps/index.svelte +++ b/packages/builder/src/pages/builder/portal/apps/index.svelte @@ -14,6 +14,7 @@ Body, } from "@budibase/bbui" import CreateAppModal from "components/start/CreateAppModal.svelte" + import UpdateAppModal from "components/start/UpdateAppModal.svelte" import api, { del } from "builderStore/api" import analytics from "analytics" import { onMount } from "svelte" @@ -30,6 +31,7 @@ let template let selectedApp let creationModal + let updatingModal let deletionModal let unpublishModal let creatingApp = false @@ -164,6 +166,11 @@ selectedApp = null } + const updateApp = async app => { + selectedApp = app + updatingModal.show() + } + const releaseLock = async app => { try { const response = await del(`/api/dev/${app.devId}/lock`) @@ -236,6 +243,7 @@ {editApp} {exportApp} {deleteApp} + {updateApp} /> {/each}
@@ -289,6 +297,12 @@ Are you sure you want to unpublish the app {selectedApp?.name}? + + + diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js index 4d045e504..3a491bfb5 100644 --- a/packages/bbui/src/index.js +++ b/packages/bbui/src/index.js @@ -38,6 +38,7 @@ export { default as MenuItem } from "./Menu/Item.svelte" export { default as Modal } from "./Modal/Modal.svelte" export { default as ModalContent } from "./Modal/ModalContent.svelte" export { default as NotificationDisplay } from "./Notification/NotificationDisplay.svelte" +export { default as Notification } from "./Notification/Notification.svelte" export { default as SideNavigation } from "./SideNavigation/Navigation.svelte" export { default as SideNavigationItem } from "./SideNavigation/Item.svelte" export { default as DatePicker } from "./Form/DatePicker.svelte" From 1dec4221e7718a54bbffc62ff9de5c711060881d Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 30 Jul 2021 13:59:04 +0100 Subject: [PATCH 090/173] Add checkbox to specify navigate action should open in a modal insteaf --- .../EventsEditor/actions/NavigateTo.svelte | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/NavigateTo.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/NavigateTo.svelte index e713ccce4..931c807ec 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/NavigateTo.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/NavigateTo.svelte @@ -1,5 +1,5 @@
- {#each $notifications as notification (notification.id)} -
-
{notification.message}
- {#if notification.icon}{/if} -
- {/each} + {#if $notificationStore} + {#key $notificationStore.id} +
+ +
+ {/key} + {/if}
diff --git a/packages/client/src/components/PeekScreenDisplay.svelte b/packages/client/src/components/PeekScreenDisplay.svelte new file mode 100644 index 000000000..236a228c3 --- /dev/null +++ b/packages/client/src/components/PeekScreenDisplay.svelte @@ -0,0 +1,78 @@ + + +{#if $peekStore.showPeek} + + +