From 004f719ddef2fd9e1d109012ce639cb1753b9d35 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 13 Jul 2021 17:27:04 +0100 Subject: [PATCH 01/56] 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 230c80b609..f96bea5474 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 100dc005c8..0e37ea03ee 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 c28309c5c8..7389b65f54 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 0c2405b728..cc86f32321 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 b93bd22c80..7e63592a38 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 6e16fd060c..4e5719e9c1 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 330fb38282..e99155ffb6 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 8056ad8cbd..88764c634c 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 a36dc5de91..0412f9c7c8 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 4611e67079..72c3593eda 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 21ec324880..64988c4dca 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 b0d2441c0a..869cf96d50 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 b40446cc89..fb7c38a101 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 dc13bb948a..d0441d5522 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 cc08b4fc4edfb9cf7538468929ef4bd0e343c2db Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 13 Jul 2021 17:28:05 +0100 Subject: [PATCH 02/56] 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 7e63592a38..55d2df6ee7 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 f6a133e5560cca6973274b6f9554d91343e1d0d1 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 14 Jul 2021 16:29:32 +0100 Subject: [PATCH 03/56] 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 0e37ea03ee..a39166a53e 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 55422ee603..9c1acfbc2a 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 f5db81e6e8..4125f04438 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 d2bbcd404b..0000000000 --- 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 74ddf87176..eb8c32bb5d 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 afd642c60dbc4cdcccf60d14c823ede797887ce4 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 14 Jul 2021 17:51:35 +0100 Subject: [PATCH 04/56] 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 4125f04438..4608ca6342 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 912659a8ad6bedfe4b02e4f6c55fed76262d2803 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 15 Jul 2021 17:57:02 +0100 Subject: [PATCH 05/56] 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 46202cbfe9..b49721a541 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 a39166a53e..f305d18d0d 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 1f1f28b917..1b48786e24 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 ff604b5a3a..2e398d8c55 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 db1fdfacd9..ebdf328cf7 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 b357eb4903..bc68121577 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) + const userId = generateGlobalUserID(profile.id) + const tenantId = await lookupTenantId({ userId }) + const db = getGlobalDB(tenantId) let dbUser - - const userId = generateGlobalUserID(profile.id) - 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 90303cb95f..147305e318 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 4051df7123..328f74c794 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 8bd635e2e3..b5225881da 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 9c1acfbc2a..98dee46997 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 a2e254461a..c01d43c869 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 da863f5493..5078218fc7 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 2d164b415d..c54a6803f0 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 6778f983c2..935ead38b6 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 764972b402..7b25da801e 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 7b6d969a98..a676afc042 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 754340046d..dece78dcff 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 a69ea35385..3a883b4a71 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 3ce794b406..2dbb956d33 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 766bc09b2f..2a83ae5d2b 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 6144397bf1..64fbfb7ea2 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 cb06b5b8d4..d56111385e 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 cc86f32321..669ed3cfad 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 55d2df6ee7..bf4ded0a29 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 4e5719e9c1..67b45a110c 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 dde92ecca5..352182c197 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 f524379266..73c10d007d 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 allUsers() { - const db = new CouchDB(GLOBAL_DB) +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) + 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 e99155ffb6..233f34576c 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 04e30fc006..c26acec6c4 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 c677f504c4..026ebf6b91 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 d0441d5522..38fafd1014 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 3ac897c10f..dfd139fb84 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 b7995dd61d58ba3dae8ffa7a34011a1653096624 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 16 Jul 2021 15:08:58 +0100 Subject: [PATCH 06/56] 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 | 16 ++++++++-------- packages/worker/src/api/routes/admin/auth.js | 2 +- .../tests/utilities/TestConfiguration.js | 5 ++++- 16 files changed, 61 insertions(+), 50 deletions(-) diff --git a/packages/auth/src/cache/user.js b/packages/auth/src/cache/user.js index b49721a541..616612a588 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 f305d18d0d..1a69bb5517 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 2e398d8c55..e8eeb6c8d0 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 bc68121577..446e9b5cba 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 147305e318..9ed837e1fe 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 b5225881da..fe9230ea29 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 ffd870c213..b5d3394fd1 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 ef91c114f6..e34fd2584e 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 7e6a777cd4..6c0d5412a7 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 3a883b4a71..ceef2a3e91 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 bf4ded0a29..02dd18360a 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 73c10d007d..a27b42d59a 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 ff9692a5ec..782c45bc16 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 bda57863f6..a62cd4db58 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -14,29 +14,29 @@ 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/auth/:tenantId/reset/update", method: "POST", }, { route: "/api/admin/configs/checklist", method: "GET", }, - { - route: "/api/apps", - method: "GET", - }, { route: "/api/admin/configs/public", method: "GET", diff --git a/packages/worker/src/api/routes/admin/auth.js b/packages/worker/src/api/routes/admin/auth.js index c26acec6c4..3fef6d7233 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 c205a45e38..593c0edd5a 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 f3156fca06129d98440a5ba794b47d6b7e69b046 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 16 Jul 2021 18:04:49 +0100 Subject: [PATCH 07/56] 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 f96bea5474..a06585744e 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 1a69bb5517..fd2395c8f5 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 9ed837e1fe..802e31d1a3 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 f8cbc21455..4a6ded819a 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 9fb984c73e..88a87e1739 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 33eb23a64d..dab56a9225 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 e34fd2584e..56762532da 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 98dee46997..7144788945 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 c01d43c869..e91ac08d65 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 c54a6803f0..f61907687a 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 320d4a3eb5..182ad51828 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 02dd18360a..3135e6374c 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 67b45a110c..11841d1b56 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 3cd99f8c4f..b00741eada 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 a27b42d59a..ba7cb97d46 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 233f34576c..e2910a2364 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 782c45bc16..fc3d3535c3 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 3fef6d7233..8a1ee76ae9 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 e302725232..5eb70759e5 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 d157285918cdacdab15bde249cc75bf078b26ff3 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 16 Jul 2021 18:24:32 +0100 Subject: [PATCH 08/56] 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 fd2395c8f5..6285649045 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 43a5d205d9..cde1d332b2 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 12570aeeb5..d335c3dfba 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 88a87e1739..9fb984c73e 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 dab56a9225..1902e17a29 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 56762532da..608a0d91b4 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 3135e6374c..9e1c2c3539 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 ba7cb97d46..a0e4e18e8d 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 d6ae82e719e327dd4b267bdcae45409726799ad7 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 16 Jul 2021 18:26:17 +0100 Subject: [PATCH 09/56] 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 eae426f251..33692030b9 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 33692030b9..ee3f2b9d4a 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 85301b3f02..4cbd4c243b 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 bbdd9949ba..61c42ea089 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 5e50c6a8cd..66d8af5892 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 ee3f2b9d4a..166935a135 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 904c27ecb0..c46b27a4de 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 05a66138f7..fed8e9e1bd 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 63368fdae96486bed72e6468f57ebc851c1e3c57 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 21 Jul 2021 18:23:49 +0100 Subject: [PATCH 19/56] 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 61c42ea089..daeb56cb6e 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 166935a135..caf31b2654 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 a90e1c7f2b..9638707169 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 4c97b49763..0c083391ad 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 fed8e9e1bd..17e9d11d3d 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 371e38fcb9d29b575de48cdb5a91771761da4421 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 21 Jul 2021 18:24:58 +0100 Subject: [PATCH 20/56] 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 4cbd4c243b..2503a99eb2 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 66d8af5892..2dc3781e30 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 c46b27a4de..0c89528c91 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 27b2a13817fa8b0a6f4ed9ebf13f38651bb6e9f8 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 12:56:47 +0100 Subject: [PATCH 21/56] 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 76417b3e0d..5e876a2369 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 d7b34f4d5e..dc90f11056 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 0e2d3d96ef..e753a23b63 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 870492c17f..abacb86b8d 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 09f05ab4b7..6962b61f99 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 22ecad1620..24aca0c396 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 0c89528c91..678aea0490 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 f45f4ea299..027eba4ef8 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 8e029d73b8..43360ddf51 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 682b0c4ee9..b274d3af91 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 ef39914ce4..2985bd23fa 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 17e9d11d3d..aa856d4a29 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 4ec6d72d3e..a015480141 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 a30a8534df..3e3a7048ca 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 aaa2c2e8e4..03bfa6ca28 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 8a19f79809..17299dc056 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 6ea105d9f9..68ca5dbc80 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 eaac412854..dfb839fe85 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 6dcd5727fb..d75c4032d7 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 cd4c6e8fde..7612d332dd 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 d56111385e..cdbac4cb4e 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 c6c1641ab5..82faea986f 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 519ab517c8..cd79d7f95b 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 321f589757..8706bcdcf6 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 c63896d32e..8d4f8c5bc9 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 2deef6b3fe..0000000000 --- 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 9cf5f58f8b..0000000000 --- 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 a9c479dcad..0000000000 --- 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 4be2ef1c95..b7b560cb6c 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 840201cbd0..f6cac4d3b2 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 0412f9c7c8..fecbc02cd7 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 0000000000..c73fb317cf --- /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 0000000000..5ba6747e68 --- /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 52ab24878b..e4580d444c 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 0000000000..0d1dc47afd --- /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 5eb70759e5..8be81c4930 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 72c3593eda..cab76b7763 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 a4f2c852d1..ddccd3db2a 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 227185ae3f..f55e7ac8bd 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 13ba2bd3bc..285cddec43 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 797b0326ed..027ad83fc3 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 acc0c7acc9..845e31d911 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 bf5b67ab1a..b753641803 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 869cf96d50..45216ae634 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 1b6a73c250b244867ad4362a2b8fd51f4afcbbe1 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 14:29:30 +0100 Subject: [PATCH 22/56] 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 6962b61f99..0acaa127cc 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 678aea0490..bcbdfe6460 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 027eba4ef8..6e40442d4b 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 8706bcdcf6..a9d761bce0 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 b21fe320c59e565cc2978a3b895ed0d4407cfe5e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 15:26:14 +0100 Subject: [PATCH 23/56] 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 bcbdfe6460..2ca0f7e2e8 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 82faea986f..c3bbaf41a2 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 ddf8930ddc..677ba5a20f 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 b7b560cb6c..19a61df2e3 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 1d6a35031118c9f13d6d012d8eaea92a74cf9178 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 16:36:21 +0100 Subject: [PATCH 24/56] 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 2985bd23fa..96e67ea84b 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 5a99520c1e..cc2fc734f8 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 0000000000..26e998e663 --- /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 677ba5a20f..e053216dd9 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 8d4f8c5bc9..cfeb3506e5 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 0000000000..5f63455db9 --- /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 0d1dc47afd..432e327115 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 ddccd3db2a..6341dffeb7 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 24012c2fba7836bfe3cb0f5dc87a2b96c5708c11 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 16:37:50 +0100 Subject: [PATCH 25/56] 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 26e998e663..fdfc49afad 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 19a61df2e3..1546cb1c0a 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 5f63455db9..a1367f4a82 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 313302cae24257b36be9cf68013466b4c5d02804 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 22:36:16 +0100 Subject: [PATCH 26/56] 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 501df2de25..57772a8281 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 3ba5b4d694..944d2ac527 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 ceef2a3e91..da7ece2e89 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 91996a7804..e4b2c7e1f0 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 07648f693f..374edfb946 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 c3bbaf41a2..f576e697ea 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 e781cf5a89..cf9e988b6c 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 a9d761bce0..e0d1e6f107 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 f55e7ac8bd..dacff30ce3 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 027ad83fc3..c8c93658f7 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 b753641803..f03f9e60be 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 224b61cf0d..fe1bb68641 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 0000000000..16701ac3d7 --- /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 026ebf6b91..f9a7257ebd 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 38fafd1014..e3e558369c 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 dfd139fb84..40fc4e227e 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 3f7bafe23d0172a582bf3aec8e0e35c660f4b948 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 22 Jul 2021 22:37:20 +0100 Subject: [PATCH 27/56] 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 da7ece2e89..1e418ed4aa 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 fe1bb68641..26023b02f2 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 f9a7257ebd..805e4b79b7 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 e3e558369c..eec2743b69 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 40fc4e227e..51628064bb 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 7588bfde39d101d59fcb67540f9809ab5dc13e72 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 16:53:22 +0100 Subject: [PATCH 28/56] 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 e1fa632003..6f1cf46606 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 6e55795de1..6dd4491bc4 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 82e43e0008bcb960151ada4af3961ba8cf768582 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 17:26:54 +0100 Subject: [PATCH 29/56] 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 9638707169..fba581a046 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 7cd03c728a..b48de48fef 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 be6561e93dddcb7619f1530e22e1bd396db4262a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 17:41:00 +0100 Subject: [PATCH 30/56] 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 e8fbb56fb0..c79725233f 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 9a9c55262900a4ea02df6fc828c42af6c5b45ed0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 19:22:04 +0100 Subject: [PATCH 31/56] 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 e0d1e6f107..9ec2922543 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 8be81c4930..8359835952 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 4d0faa38e39cf0b56de743b67f19e9bd3f0ddf0d Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 20:01:53 +0100 Subject: [PATCH 32/56] 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 57772a8281..602520ef1a 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 ffd8c6b9e3..96c2674317 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 e91ac08d65..4e13851cfe 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 eb8c32bb5d..92734c5e7b 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 52c680f65a..9f69664ffb 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 a40391b396..dbc2f424d4 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 9ec2922543..cb0be9e2ad 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 c63ee57bbd1171a6dfd4c9e3d1b0f6692b529341 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 20:03:11 +0100 Subject: [PATCH 33/56] 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 602520ef1a..7850a03fff 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 cb0be9e2ad..f6bf76c9c1 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 a630bfb515a7f2d8c064c0978b527046ba4f3f4b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 23 Jul 2021 20:08:16 +0100 Subject: [PATCH 34/56] 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 e753a23b63..9cc878bba9 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 4606248ffd79526209762eaafd0a60ef77464104 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 13:19:47 +0100 Subject: [PATCH 35/56] 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 7850a03fff..4650b6f122 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 caf31b2654..0e0fd27c78 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 96c2674317..c24d0057f0 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 4e13851cfe..15c4c9bf7a 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 c2eb19e101..c1d39acbd5 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 dbc2f424d4..682c28feae 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 fc3d3535c3..a7b6c5032c 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 b48de48fef..1eb2064b8a 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 b00741eada..1aae07241e 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 224a63ccb2a686762a9c206fdfc413788cf71bda Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 13:20:42 +0100 Subject: [PATCH 36/56] 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 4650b6f122..259f24406c 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 d03229cabaf9046bf6051c3837e9a490632c27e5 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 13:23:11 +0100 Subject: [PATCH 37/56] 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 1546cb1c0a..23393a3258 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 dd1f1fec3b096e0f1eb497476f2fcc1658ac2b01 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 13:31:39 +0100 Subject: [PATCH 38/56] 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 23393a3258..34260dee7f 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 a279e5e5044f0078eebcb0b92cdfaa955184b8bf Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 13:40:50 +0100 Subject: [PATCH 39/56] 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 7527343ba9..458097bdb0 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 6e40442d4b..5161625510 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 219962cb8ac777d3ffc66ce4567a4d22fc9497a9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Jul 2021 16:17:56 +0100 Subject: [PATCH 40/56] Adding ability to disable/enable multi-tenancy flags easily. --- package.json | 4 +- packages/server/package.json | 7 +- packages/server/scripts/multiTenancy.js | 8 + packages/server/yarn.lock | 1050 ++++++++++++++++++++++- packages/worker/package.json | 7 +- packages/worker/scripts/multiTenancy.js | 8 + packages/worker/yarn.lock | 617 ++++++++++++- 7 files changed, 1663 insertions(+), 38 deletions(-) create mode 100644 packages/server/scripts/multiTenancy.js create mode 100644 packages/worker/scripts/multiTenancy.js diff --git a/package.json b/package.json index 4f545d935f..8fbed6175a 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,8 @@ "test:e2e": "lerna run cy:test", "test:e2e:ci": "lerna run cy:ci", "build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -", - "build:docker:develop": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -" + "build:docker:develop": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", + "multi:enable": "lerna run multi:enable", + "multi:disable": "lerna run multi:disable" } } diff --git a/packages/server/package.json b/packages/server/package.json index c64c27a74b..8c2870a6e4 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -23,7 +23,9 @@ "format": "prettier --config ../../.prettierrc.json 'src/**/*.ts' --write", "lint": "eslint --fix src/", "lint:fix": "yarn run format && yarn run lint", - "initialise": "node scripts/initialise.js" + "initialise": "node scripts/initialise.js", + "multi:enable": "node scripts/multiTenancy.js enable", + "multi:disable": "node scripts/multiTenancy.js disable" }, "jest": { "preset": "ts-jest", @@ -135,7 +137,8 @@ "supertest": "^4.0.2", "ts-jest": "^27.0.3", "ts-node": "^10.0.0", - "typescript": "^4.3.4" + "typescript": "^4.3.4", + "update-dotenv": "^1.1.1" }, "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc" } diff --git a/packages/server/scripts/multiTenancy.js b/packages/server/scripts/multiTenancy.js new file mode 100644 index 0000000000..89ee7398f3 --- /dev/null +++ b/packages/server/scripts/multiTenancy.js @@ -0,0 +1,8 @@ +#!/usr/bin/env node +const updateDotEnv = require("update-dotenv") + +const arg = process.argv.slice(2)[0] + +updateDotEnv({ + MULTI_TENANCY: arg === "enable" ? "1" : "", +}).then(() => console.log("Updated server!")) diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 711eba707f..c7f7bca0a1 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/spectrum-css-workflow-icons@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4" + integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w== + "@azure/abort-controller@^1.0.0": version "1.0.4" resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.4.tgz#fd3c4d46c8ed67aace42498c8e2270960250eafd" @@ -1141,6 +1146,140 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@budibase/auth@^0.9.79-alpha.4": + version "0.9.79" + resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.79.tgz#416271ffc55e84116550469656bf151a7734a90f" + integrity sha512-ENh099tYeUfVExsAeoxwMh2ODioKQGPteK9LJiU5hMdM4Oi7pyImu287BgKpTIheB+WtadT4e21VpPaJ62APEw== + dependencies: + aws-sdk "^2.901.0" + bcryptjs "^2.4.3" + ioredis "^4.27.1" + jsonwebtoken "^8.5.1" + koa-passport "^4.1.4" + lodash "^4.17.21" + node-fetch "^2.6.1" + passport-google-auth "^1.0.2" + passport-google-oauth "^2.0.0" + passport-jwt "^4.0.0" + passport-local "^1.0.0" + sanitize-s3-objectkey "^0.0.1" + tar-fs "^2.1.1" + uuid "^8.3.2" + zlib "^1.0.5" + +"@budibase/bbui@^0.9.79": + version "0.9.79" + resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.79.tgz#c033ba0af41cb584d2657a8353f9887328f6633f" + integrity sha512-XxUJSPGd2FZDFdbNOeMUXohhID5h3DVq9XyKTe6WhYax4m2da/2WTENJ16UFvmfA+yxLN1qSDeweq9vw2zCahQ== + dependencies: + "@adobe/spectrum-css-workflow-icons" "^1.2.1" + "@spectrum-css/actionbutton" "^1.0.1" + "@spectrum-css/actiongroup" "^1.0.1" + "@spectrum-css/avatar" "^3.0.2" + "@spectrum-css/button" "^3.0.1" + "@spectrum-css/buttongroup" "^3.0.2" + "@spectrum-css/checkbox" "^3.0.2" + "@spectrum-css/dialog" "^3.0.1" + "@spectrum-css/divider" "^1.0.1" + "@spectrum-css/dropzone" "^3.0.2" + "@spectrum-css/fieldgroup" "^3.0.2" + "@spectrum-css/fieldlabel" "^3.0.1" + "@spectrum-css/icon" "^3.0.1" + "@spectrum-css/illustratedmessage" "^3.0.2" + "@spectrum-css/inputgroup" "^3.0.2" + "@spectrum-css/label" "^2.0.10" + "@spectrum-css/link" "^3.1.1" + "@spectrum-css/menu" "^3.0.1" + "@spectrum-css/modal" "^3.0.1" + "@spectrum-css/pagination" "^3.0.3" + "@spectrum-css/picker" "^1.0.1" + "@spectrum-css/popover" "^3.0.1" + "@spectrum-css/progressbar" "^1.0.2" + "@spectrum-css/progresscircle" "^1.0.2" + "@spectrum-css/radio" "^3.0.2" + "@spectrum-css/search" "^3.0.2" + "@spectrum-css/sidenav" "^3.0.2" + "@spectrum-css/statuslight" "^3.0.2" + "@spectrum-css/switch" "^1.0.2" + "@spectrum-css/table" "^3.0.1" + "@spectrum-css/tabs" "^3.0.1" + "@spectrum-css/tags" "^3.0.2" + "@spectrum-css/textfield" "^3.0.1" + "@spectrum-css/toast" "^3.0.1" + "@spectrum-css/tooltip" "^3.0.3" + "@spectrum-css/treeview" "^3.0.2" + "@spectrum-css/typography" "^3.0.1" + "@spectrum-css/underlay" "^2.0.9" + "@spectrum-css/vars" "^3.0.1" + dayjs "^1.10.4" + svelte-flatpickr "^3.1.0" + svelte-portal "^1.0.0" + +"@budibase/client@^0.9.79-alpha.4": + version "0.9.79" + resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.79.tgz#d1c8d51e9121f81902cfb31d3b685c8061f272a2" + integrity sha512-//Yqm5Qki6BmBe5W2Tz8GONdkFjdD1jkIU7pcLYKqdZJWEQIrX6T/xNvYvZVhw7Dx5bwSZRjFwzm7jLoiyHBIA== + dependencies: + "@budibase/bbui" "^0.9.79" + "@budibase/standard-components" "^0.9.79" + "@budibase/string-templates" "^0.9.79" + regexparam "^1.3.0" + shortid "^2.2.15" + svelte-spa-router "^3.0.5" + +"@budibase/handlebars-helpers@^0.11.4": + version "0.11.5" + resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.5.tgz#e9cc90a44e94ad536992cf10906829b633e94bc5" + integrity sha512-ZxpyNtTHxS8Y+yTicbgWvYDAydooUSjOf3Y+wmTE2d4NpDgO0g0IjepLfZV+KASv9XBr//ylJdjE4hClX9NTFw== + dependencies: + array-sort "^1.0.0" + define-property "^2.0.2" + extend-shallow "^3.0.2" + "falsey" "^1.0.0" + for-in "^1.0.2" + get-object "^0.2.0" + get-value "^3.0.1" + handlebars "^4.7.7" + handlebars-utils "^1.0.6" + has-value "^2.0.2" + helper-date "^1.0.1" + helper-markdown "^1.0.0" + helper-md "^0.2.2" + html-tag "^2.0.0" + is-even "^1.0.0" + is-glob "^4.0.1" + kind-of "^6.0.3" + micromatch "^3.1.5" + relative "^3.0.2" + striptags "^3.1.1" + to-gfm-code-block "^0.1.1" + year "^0.2.1" + +"@budibase/standard-components@^0.9.79", "@budibase/standard-components@^0.9.79-alpha.4": + version "0.9.79" + resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.79.tgz#24206642e0cdc655ea3a99ed5e9402ec4f6b3ba8" + integrity sha512-ZWhmBZ1iG+CjGMEvT/jtugMMgA1n88UYcOfP3BSP2P3eA16DubyU9hH9OyJHbGPzDHLoBF6vuS/5ZPZCkOKppw== + dependencies: + "@budibase/bbui" "^0.9.79" + "@spectrum-css/link" "^3.1.3" + "@spectrum-css/page" "^3.0.1" + "@spectrum-css/vars" "^3.0.1" + apexcharts "^3.22.1" + dayjs "^1.10.5" + svelte-apexcharts "^1.0.2" + svelte-flatpickr "^3.1.0" + +"@budibase/string-templates@^0.9.79", "@budibase/string-templates@^0.9.79-alpha.4": + version "0.9.79" + resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.79.tgz#bb75a7433a7cfda1fc488283f35e47879b799fcc" + integrity sha512-hkAne5mx7mj8+osXFt45VwgLKSa94uQOGOb4R8uv9WNzvk4RzcjBfRzJxggv29FUemItrAeZpSh+Um6yugFI+w== + dependencies: + "@budibase/handlebars-helpers" "^0.11.4" + dayjs "^1.10.4" + handlebars "^4.7.6" + handlebars-utils "^1.0.6" + lodash "^4.17.20" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1947,6 +2086,205 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@spectrum-css/actionbutton@^1.0.1": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.3.tgz#8f7342a69b303c5acdcfa0a59f5e9267b9f3cb30" + integrity sha512-P9qoCPSiZ1SB6ZYqK5hub0vGty00YYqonQE0KTjtb1i+T1nYR/87vWqVPERx9j63uhgZncjwFYaThTvRqye7eQ== + +"@spectrum-css/actiongroup@^1.0.1": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/actiongroup/-/actiongroup-1.0.3.tgz#4713ce65e6f5c72c404a7b638fbc3b4fd7e3874f" + integrity sha512-NlB9Q4ZlWixXxymoPIYG6g2hkwAGKFodHWPFfxHD8ddkjXWRB9G2akUP7cfsJ4DcYn4VisUORCOYQwqDiSmboQ== + +"@spectrum-css/avatar@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/avatar/-/avatar-3.0.2.tgz#4f1826223eae330e64b6d3cc899e9bc2e98dac95" + integrity sha512-wEczvSqxttTWSiL3cOvXV/RmGRwSkw2w6+slcHhnf0kb7ovymMM+9oz8vvEpEsSeo5u598bc+7ktrKFpAd6soQ== + +"@spectrum-css/button@^3.0.1": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84" + integrity sha512-6CnLPqqtaU/PcSSIGeGRi0iFIIxIUByYLKFO6zn5NEUc12KQ28dJ4PLwB6WBa0L8vRoAGlnWWH2ZZweTijbXgg== + +"@spectrum-css/buttongroup@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/buttongroup/-/buttongroup-3.0.3.tgz#719d868845ac9d2c4f939c1b9f6044507902d5aa" + integrity sha512-eXl8U4QWMWXqyTu654FdQdEGnmczgOYlpIFSHyCMVjhtPqZp2xwnLFiGh6LKw+bLio6eeOZ0L+vpk1GcoYqgkw== + +"@spectrum-css/checkbox@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.3.tgz#8577067fc8b97e4609f92bd242364937a533a7bb" + integrity sha512-QVG9uMHq+lh70Dh6mDNnY+vEvNz2p7VC6xgLfDYfijp2qeiqYPq72fQK6p/SiyqPk96ZACzNRwgeROU6Xf6Wgg== + +"@spectrum-css/dialog@^3.0.1": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/dialog/-/dialog-3.0.3.tgz#7715a4ea435e753afb623d99ca5917ed1bcd6f34" + integrity sha512-AhmKgfRIVyTe3ABiJ8lLUQL34VB/H6fN16na2LlbDRJvyRMzkdN1Xf2i6U3f4OMd3qQ8Gm5xat4BvMxIQPBAUQ== + +"@spectrum-css/divider@^1.0.1": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/divider/-/divider-1.0.3.tgz#639e2ebaa0834efa40f42397668bbd5c153ea385" + integrity sha512-Zy4Rn40w8UtzMh3wx/U9+CepSCpm1aOCGftHgWDub0XZuVTzh0c1WwyzTuLCx2Hf21z5VRGNiDh8bGEEzSbtNA== + dependencies: + "@spectrum-css/vars" "^3.0.2" + +"@spectrum-css/dropzone@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/dropzone/-/dropzone-3.0.3.tgz#aee71697a2c195947599d7541b858c3c198741dc" + integrity sha512-ujrswdtB6bHigklyHsm6zomFd6rUnKJ3xVVRjroVF4+ouK4DxK5tX0iVd0EW6AOfOjx4Cc28uyFot3fpxp+MQw== + +"@spectrum-css/fieldgroup@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/fieldgroup/-/fieldgroup-3.0.3.tgz#85d85da048d08200f25ceab378026dd2b11e0bb2" + integrity sha512-wXUXTXN1CPnR7M4Ltd+3uh7BfcNGUV1+Xe0/h0tCpq/j+S2Sd4xo7/pUMdU19sIDrAAtpEFp1tt+nouHcU5HGQ== + +"@spectrum-css/fieldlabel@^3.0.1": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.3.tgz#f73c04d20734d4718ffb620dc46458904685b449" + integrity sha512-nEvIkEXCD5n4fW67Unq6Iu7VXoauEd/JGpfTY02VsC5p4FJLnwKfPDbJUuUsqClAxqw7nAsmXVKtn4zQFf5yPQ== + +"@spectrum-css/icon@^3.0.1": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.3.tgz#5c612822380927087aebd526855d82ed2c3e2cba" + integrity sha512-hyloKOejPCXhP3MBNsm3SjR9j8xT1R1S19p32KW/0Qhj+VMUtfyEPmevyFptpn7wcovQccdl/vZVIVDuML/imw== + +"@spectrum-css/illustratedmessage@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/illustratedmessage/-/illustratedmessage-3.0.2.tgz#6a480be98b027e050b086e7899e40d87adb0a8c0" + integrity sha512-dqnE8X27bGcO0HN8+dYx8O4o0dNNIAqeivOzDHhe2El+V4dTzMrNIerF6G0NLm3GjVf6XliwmitsZK+K6FmbtA== + +"@spectrum-css/inputgroup@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.3.tgz#00c9a370ddc2c55cf0f37dd6069faa9501fd7eb5" + integrity sha512-FqRJTiLL7jiGfzDVXWUGVLqHryJjCcqQIrqAi+Tq0oenapzsBe2qc/zIrKgh2wtMI+NTIBLXTECvij3L1HlqAg== + +"@spectrum-css/label@^2.0.10": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.10.tgz#2368651d7636a19385b5d300cdf6272db1916001" + integrity sha512-xCbtEiQkZIlLdWFikuw7ifDCC21DOC/KMgVrrVJHXFc4KRQe9LTZSqmGF3tovm+CSq1adE59mYoTbojVQ9YuEQ== + +"@spectrum-css/link@^3.1.1", "@spectrum-css/link@^3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.3.tgz#b0e560a7e0acdb4a2b329b6669696aa3438f5993" + integrity sha512-8Pmy5d73MwKcATTPaj+fSrZYnIw7GmfX40AvpC1xx5LauOxsbUb4AVNp1kn2H8rrOBmxF7czyhoBBhEiv66QUg== + +"@spectrum-css/menu@^3.0.1": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.3.tgz#46a9b221bb5f470a2f8a934bdfd512d84d2fdc4d" + integrity sha512-qKA9J/MrikNKIpCEHsAazG2vY3im5tjGCmo6p9Pdnu8/aQMsiuZDHZayukeCttJUZkrb9guDVL9OIHlK5RZvcQ== + +"@spectrum-css/modal@^3.0.1": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/modal/-/modal-3.0.2.tgz#58b6621cab65f90788d310374f40df1f7090473f" + integrity sha512-YnIivJhoaao7Otu+HV7sgebPyFbO6sd/oMvTN/Rb2wwgnaMnIIuIRdGandSrcgotN2uNgs+P0knG6mv/xA1/dg== + +"@spectrum-css/page@^3.0.1": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.2.tgz#8f0c03d25f5565fb13115541a8fcaf0e1d3a8ee0" + integrity sha512-lCXWjonLwYBg8FHUEkiFX0Mmfk+9Uivgvxq0DTulPlWrJcULTwjaOiY28/YBz7Fy1wuv/0KORbkPRALpYldBZg== + dependencies: + "@spectrum-css/vars" "^3.0.2" + +"@spectrum-css/pagination@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/pagination/-/pagination-3.0.3.tgz#b204c3ada384c4af751a354bc428346d82eeea65" + integrity sha512-OJ/v9GeNXJOZ9Yr9LDBYPrR2NCiLOWP9wANT/a5sqFuugRnQbn/HYMnRp9TBxwpDY6ihaPo0T/wi7kLiAJFdDw== + +"@spectrum-css/picker@^1.0.1": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.3.tgz#21379bcf8ae94277deeb6ad65dcd9e2bbfacb487" + integrity sha512-oHLGxBx5BwVCSGo7/T1C9PTHX1+/5AmVjyLiTJ4UoIdSJmOERw9YcRZbcGZgBJNWbxcjr4TyGtwj1EcSjEy97w== + +"@spectrum-css/popover@^3.0.1": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.3.tgz#6fb69873474fb968afb738eacb9e121f93e83a09" + integrity sha512-KvmXv4TV19FBx39KfmgVlDYtvtBqv/8RRK7RRLDDHGViTxZtShjVsVpwIgfkfgn4iJztCnXpWzFqRXWUu2XCpQ== + +"@spectrum-css/progressbar@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/progressbar/-/progressbar-1.0.3.tgz#f70bcc38a2a21cff2f422ec825724ebbb9455e67" + integrity sha512-vJHplefUuy8+NjCw1X7fLbqHVGNVBpvGFXNAeaIj4SFf4ygxiUq/5c9iRhhsCQixEsJlfD/b7BnGXU7BUDkr6Q== + +"@spectrum-css/progresscircle@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/progresscircle/-/progresscircle-1.0.2.tgz#258ea9170fb70f795edda03e38a61d93bef4487c" + integrity sha512-JLULpyzjIY95lzlWR1yE1gv4l1K6p+scQ+edmuZZUHBzwM3pUtkvHJmUlA9TYdResUYW6Uka60VRdY6lZ8gnFQ== + +"@spectrum-css/radio@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/radio/-/radio-3.0.3.tgz#25c3bc5e9c30a8a8ae728717b7c7fb736cdae640" + integrity sha512-LaLGfz/eGNR2iyqouXYILIA+pKRqF769iPdwM0REm5RpWvMQDD7rPZ/kWlg18owjaFsyllEp25gEjmhRJIIVOw== + +"@spectrum-css/search@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/search/-/search-3.0.3.tgz#3415dc106aca0d5dd996e87084a1b47c2b95a882" + integrity sha512-kdLpKTt0obljuhS1q1tukujRlvSs8sBwij76D4Qp8KpMzwePfZyvv1kYzuWPNZfTeISxWsmyZ6Wxd1uvzjn+UA== + +"@spectrum-css/sidenav@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/sidenav/-/sidenav-3.0.3.tgz#132141fbd2500a927c312fa4e1d712c438b3d597" + integrity sha512-cQ+CgwjxGftrcc79i1XnGd66QTl7H7zopSU0UTV4Qq7hvqfrjjWxfZ6b+3tezrrhNlDope1ff9o8sm67PsPXtg== + +"@spectrum-css/statuslight@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/statuslight/-/statuslight-3.0.2.tgz#dc54b6cd113413dcdb909c486b5d7bae60db65c5" + integrity sha512-xodB8g8vGJH20XmUj9ZsPlM1jHrGeRbvmVXkz0q7YvQrYAhim8pP3W+XKKZAletPFAuu8cmUOc6SWn6i4X4z6w== + +"@spectrum-css/switch@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/switch/-/switch-1.0.2.tgz#f0b4c69271964573e02b08e90998096e49e1de44" + integrity sha512-zqmHpgWPNg1gEwdUNFYV3CBX5JaeALfIqcJIxE0FLZqr9d1C4+oLE0ItIFzt1bwr4bFAOmkEpvtiY+amluzGxQ== + +"@spectrum-css/table@^3.0.1": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/table/-/table-3.0.3.tgz#7f7f19905ef3275cbf907ce3a5818e63c30b2caf" + integrity sha512-nxwzVjLPsXoY/v4sdxOVYLcC+cEbGgJyLcLclT5LT9MGSbngFeUMJzzVR4EvehzuN4dH7hrATG7Mbuq29Mf0Hg== + +"@spectrum-css/tabs@^3.0.1": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.0.3.tgz#51dd6f168c897b0fdc3a7e9f901df7bd2288b4fc" + integrity sha512-iLP1I72bJWz9APdQB1jiw+pOv5a7N+hYOCJvRoc56Us/hJKVzowkyGRe3uH+8v36nCG9bHxiAQNLoU8eXisVrg== + +"@spectrum-css/tags@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.3.tgz#fc76d2735cdc442de91b7eb3bee49a928c0767ac" + integrity sha512-SL8vPxVDfWcY5VdIuyl0TImEXcOU1I7yCyXkk7MudMwfnYs81FaIyY32hFV9OHj0Tz/36UzRzc7AVMSuRQ53pw== + +"@spectrum-css/textfield@^3.0.1": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.2.tgz#907f62d2dc82852dd6236a820be99e252b531631" + integrity sha512-nkFgAb0cP4jUodkUBErMNfyF78jJLtgL1Mrr9/rvGpGobo10IAbb8zZY4CkZ64o8XmMy/85+wZTKcx+KHatqpg== + +"@spectrum-css/toast@^3.0.1": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/toast/-/toast-3.0.3.tgz#97c1527384707600832ecda35643ed304615250f" + integrity sha512-CjLeaMs+cjUXojCCRtbj0YkD2BoZW16kjj2o5omkEpUTjA34IJ8xJ1a+CCtDILWekhXvN0MBN4sbumcnwcnx8w== + +"@spectrum-css/tooltip@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/tooltip/-/tooltip-3.0.3.tgz#26b8ca3b3d30e29630244d85eb4fc11d0c841281" + integrity sha512-ztRF7WW1FzyNavXBRc+80z67UoOrY9wl3cMYsVD3MpDnyxdzP8cjza1pCcolKBaFqRTcQKkxKw3GWtGICRKR5A== + +"@spectrum-css/treeview@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/treeview/-/treeview-3.0.3.tgz#aeda5175158b9f8d7529cb2b394428eb2a428046" + integrity sha512-D5gGzZC/KtRArdx86Mesc9+99W9nTbUOeyYGqoJoAfJSOttoT6Tk5CrDvlCmAqjKf5rajemAkGri1ChqvUIwkw== + +"@spectrum-css/typography@^3.0.1": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38" + integrity sha512-5ZOLmQe0edzsDMyhghUd4hBb5uxGsFrxzf+WasfcUw9klSfTsRZ09n1BsaaWbgrLjlMQ+EEHS46v5VNo0Ms2CA== + +"@spectrum-css/underlay@^2.0.9": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@spectrum-css/underlay/-/underlay-2.0.10.tgz#8b75b646605a311850f6620caa18d4996cd64ed7" + integrity sha512-PmsmkzeGD/rY4pp3ILXHt9w8BW7uaEqXe08hQRS7rGki7wqCpG4mE0/8N3yEcA3QxWQclmG9gdkg5uz6wMmYzA== + +"@spectrum-css/vars@^3.0.1", "@spectrum-css/vars@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.2.tgz#ea9062c3c98dfc6ba59e5df14a03025ad8969999" + integrity sha512-vzS9KqYXot4J3AEER/u618MXWAS+IoMvYMNrOoscKiLLKYQWenaueakUWulFonToPd/9vIpqtdbwxznqrK5qDw== + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -2560,6 +2898,18 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +apexcharts@^3.19.2, apexcharts@^3.22.1: + version "3.27.3" + resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.27.3.tgz#1b921ac64dea2f28a2b1aad4b396b38464223849" + integrity sha512-1ZrqiQT0VahkqW0kVjf5QVURYGaHMlGN08BoIZG2c2U/gY2AtnEoFN4r9q4d/pYYYKvI9AyLBHq0otzcVGrHAw== + dependencies: + svg.draggable.js "^2.2.2" + svg.easing.js "^2.0.0" + svg.filter.js "^2.0.2" + svg.pathmorphing.js "^0.1.3" + svg.resize.js "^1.4.3" + svg.select.js "^3.0.1" + arangojs@7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/arangojs/-/arangojs-7.2.0.tgz#e576926b4b3469c5a130cceba45fada8b5f015d1" @@ -2583,7 +2933,7 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argparse@^1.0.7: +argparse@^1.0.10, argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== @@ -2630,6 +2980,15 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= +array-sort@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" + integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== + dependencies: + default-compare "^1.0.0" + get-value "^2.0.6" + kind-of "^5.0.2" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -2687,6 +3046,13 @@ async@>=0.6.0: resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== +async@~2.1.4: + version "2.1.5" + resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc" + integrity sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw= + dependencies: + lodash "^4.14.0" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2702,6 +3068,13 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== +autolinker@~0.28.0: + version "0.28.1" + resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.28.1.tgz#0652b491881879f0775dace0cdca3233942a4e47" + integrity sha1-BlK0kYgYefB3XazgzcoyM5QqTkc= + dependencies: + gulp-header "^1.7.1" + aws-sdk@^2.767.0: version "2.924.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.924.0.tgz#daefcd11729638d9c9279633a0cc5ba1c98fee64" @@ -2717,6 +3090,21 @@ aws-sdk@^2.767.0: uuid "3.3.2" xml2js "0.4.19" +aws-sdk@^2.901.0: + version "2.953.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.953.0.tgz#2ba87da084164bcb8e325e67356b8372e54cb5d9" + integrity sha512-CCsJm+ggE1HQ2fkCto+JRqJyET81Vw8eZr/KnNw19jqRiAsXNj5GqAh1JNyTCHEif8PcjREtP7o3y093ylSUyg== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -2898,6 +3286,11 @@ base64-js@^1.0.2, base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64url@3.x.x: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -2918,7 +3311,7 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -bcryptjs@2.4.3: +bcryptjs@2.4.3, bcryptjs@^2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= @@ -2963,6 +3356,15 @@ bl@^3.0.0: dependencies: readable-stream "^3.0.1" +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + bluebird@^3.5.1: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -3322,6 +3724,11 @@ chokidar@^3.2.2: optionalDependencies: fsevents "~2.3.1" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -3510,6 +3917,13 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +concat-with-sourcemaps@*: + version "1.1.0" + resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" + integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== + dependencies: + source-map "^0.6.1" + configstore@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" @@ -3723,11 +4137,23 @@ date-utils@*: resolved "https://registry.yarnpkg.com/date-utils/-/date-utils-1.2.21.tgz#61fb16cdc1274b3c9acaaffe9fc69df8720a2b64" integrity sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q= +date.js@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.3.tgz#ef1e92332f507a638795dbb985e951882e50bbda" + integrity sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw== + dependencies: + debug "~3.1.0" + dateformat@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +dayjs@^1.10.4, dayjs@^1.10.5: + version "1.10.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" + integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -3861,6 +4287,13 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +default-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" + integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== + dependencies: + kind-of "^5.0.2" + default-shell@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/default-shell/-/default-shell-1.0.1.tgz#752304bddc6174f49eb29cb988feea0b8813c8bc" @@ -4130,7 +4563,7 @@ encoding-down@^6.3.0: level-codec "^9.0.0" level-errors "^2.0.0" -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -4144,6 +4577,11 @@ end-stream@~0.1.0: dependencies: write-stream "~0.4.3" +ent@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" + integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= + errno@~0.1.1, errno@~0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" @@ -4639,6 +5077,11 @@ falafel@^1.0.1: isarray "0.0.1" object-keys "^1.0.6" +"falsey@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/falsey/-/falsey-1.0.0.tgz#71bdd775c24edad9f2f5c015ce8be24400bb5d7d" + integrity sha512-zMDNZ/Ipd8MY0+346CPvhzP1AsiVyNfTOayJza4reAIWf72xbkuFUDcJNxSAsQE1b9Bu0wijKb8Ngnh/a7fI5w== + fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -4855,6 +5298,11 @@ flat-cache@^2.0.1: rimraf "2.6.3" write "1.0.3" +flatpickr@^4.5.2: + version "4.6.9" + resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.9.tgz#9a13383e8a6814bda5d232eae3fcdccb97dc1499" + integrity sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw== + flatstr@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" @@ -4954,6 +5402,11 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-exists-sync@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" + integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= + fs-extra@8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -5010,6 +5463,14 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" +get-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/get-object/-/get-object-0.2.0.tgz#d92ff7d5190c64530cda0543dac63a3d47fe8c0c" + integrity sha1-2S/31RkMZFMM2gVD2sY6PUf+jAw= + dependencies: + is-number "^2.0.2" + isobject "^0.2.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -5057,6 +5518,13 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +get-value@^3.0.0, get-value@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8" + integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA== + dependencies: + isobject "^3.0.1" + getopts@2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b" @@ -5146,6 +5614,32 @@ globby@^11.0.3: merge2 "^1.3.0" slash "^3.0.0" +google-auth-library@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.10.0.tgz#6e15babee85fd1dd14d8d128a295b6838d52136e" + integrity sha1-bhW6vuhf0d0U2NEoopW2g41SE24= + dependencies: + gtoken "^1.2.1" + jws "^3.1.4" + lodash.noop "^3.0.1" + request "^2.74.0" + +google-p12-pem@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-0.1.2.tgz#33c46ab021aa734fa0332b3960a9a3ffcb2f3177" + integrity sha1-M8RqsCGqc0+gMys5YKmj/8svMXc= + dependencies: + node-forge "^0.7.1" + +googleapis@^16.0.0: + version "16.1.0" + resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-16.1.0.tgz#0f19f2d70572d918881a0f626e3b1a2fa8629576" + integrity sha1-Dxny1wVy2RiIGg9ibjsaL6hilXY= + dependencies: + async "~2.1.4" + google-auth-library "~0.10.0" + string-template "~1.0.0" + got@^8.3.1: version "8.3.2" resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" @@ -5191,6 +5685,45 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1. resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== +gtoken@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.3.tgz#5509571b8afd4322e124cf66cf68115284c476d8" + integrity sha512-wQAJflfoqSgMWrSBk9Fg86q+sd6s7y6uJhIvvIPz++RElGlMtEqsdAR2oWwZ/WTEtp7P9xFbJRrT976oRgzJ/w== + dependencies: + google-p12-pem "^0.1.0" + jws "^3.0.0" + mime "^1.4.1" + request "^2.72.0" + +gulp-header@^1.7.1: + version "1.8.12" + resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.12.tgz#ad306be0066599127281c4f8786660e705080a84" + integrity sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ== + dependencies: + concat-with-sourcemaps "*" + lodash.template "^4.4.0" + through2 "^2.0.0" + +handlebars-utils@^1.0.2, handlebars-utils@^1.0.4, handlebars-utils@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9" + integrity sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw== + dependencies: + kind-of "^6.0.0" + typeof-article "^0.1.1" + +handlebars@^4.7.6, handlebars@^4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -5254,6 +5787,14 @@ has-value@^1.0.0: has-values "^1.0.0" isobject "^3.0.0" +has-value@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-2.0.2.tgz#d0f12e8780ba8e90e66ad1a21c707fdb67c25658" + integrity sha512-ybKOlcRsK2MqrM3Hmz/lQxXHZ6ejzSPzpNabKB45jb5qDgJvKPa3SdapTsTLwEb9WltgWpOmNax7i+DzNOk4TA== + dependencies: + get-value "^3.0.0" + has-values "^2.0.1" + has-values@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" @@ -5267,6 +5808,13 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has-values@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-2.0.1.tgz#3876200ff86d8a8546a9264a952c17d5fc17579d" + integrity sha512-+QdH3jOmq9P8GfdjFg0eJudqx1FqU62NQJ4P16rOEHeRdl7ckgwn6uqQjzYE0ZoHVV/e5E2esuJ5Gl5+HUW19w== + dependencies: + kind-of "^6.0.2" + has-yarn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" @@ -5279,6 +5827,39 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +helper-date@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/helper-date/-/helper-date-1.0.1.tgz#12fedea3ad8e44a7ca4c4efb0ff4104a5120cffb" + integrity sha512-wU3VOwwTJvGr/w5rZr3cprPHO+hIhlblTJHD6aFBrKLuNbf4lAmkawd2iK3c6NbJEvY7HAmDpqjOFSI5/+Ey2w== + dependencies: + date.js "^0.3.1" + handlebars-utils "^1.0.4" + moment "^2.18.1" + +helper-markdown@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/helper-markdown/-/helper-markdown-1.0.0.tgz#ee7e9fc554675007d37eb90f7853b13ce74f3e10" + integrity sha512-AnDqMS4ejkQK0MXze7pA9TM3pu01ZY+XXsES6gEE0RmCGk5/NIfvTn0NmItfyDOjRAzyo9z6X7YHbHX4PzIvOA== + dependencies: + handlebars-utils "^1.0.2" + highlight.js "^9.12.0" + remarkable "^1.7.1" + +helper-md@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/helper-md/-/helper-md-0.2.2.tgz#c1f59d7e55bbae23362fd8a0e971607aec69d41f" + integrity sha1-wfWdflW7riM2L9ig6XFgeuxp1B8= + dependencies: + ent "^2.2.0" + extend-shallow "^2.0.1" + fs-exists-sync "^0.1.0" + remarkable "^1.6.2" + +highlight.js@^9.12.0: + version "9.18.5" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" + integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -5308,6 +5889,14 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-tag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/html-tag/-/html-tag-2.0.0.tgz#36c3bc8d816fd30b570d5764a497a641640c2fed" + integrity sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g== + dependencies: + is-self-closing "^1.0.1" + kind-of "^6.0.0" + http-assert@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.4.1.tgz#c5f725d677aa7e873ef736199b89686cceb37878" @@ -5581,6 +6170,22 @@ ioredis@^4.27.0: redis-parser "^3.0.0" standard-as-callback "^2.1.0" +ioredis@^4.27.1: + version "4.27.6" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.27.6.tgz#a53d427d3fe75fbd10ed7ad150ce00559df8dcf8" + integrity sha512-6W3ZHMbpCa8ByMyC1LJGOi7P2WiOKP9B3resoZOVLDhi+6dDBOW+KNsRq3yI36Hmnb2sifCxHX+YSarTeXh48A== + dependencies: + cluster-key-slot "^1.1.0" + debug "^4.3.1" + denque "^1.1.0" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + p-map "^2.1.0" + redis-commands "1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -5702,6 +6307,13 @@ is-docker@^2.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-even@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-even/-/is-even-1.0.0.tgz#76b5055fbad8d294a86b6a949015e1c97b717c06" + integrity sha1-drUFX7rY0pSoa2qUkBXhyXtxfAY= + dependencies: + is-odd "^0.1.2" + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -5787,6 +6399,13 @@ is-number-object@^1.0.4: resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== +is-number@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= + dependencies: + kind-of "^3.0.2" + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -5809,6 +6428,13 @@ is-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== +is-odd@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-0.1.2.tgz#bc573b5ce371ef2aad6e6f49799b72bef13978a7" + integrity sha1-vFc7XONx7yqtbm9JeZtyvvE5eKc= + dependencies: + is-number "^3.0.0" + is-path-inside@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -5844,6 +6470,13 @@ is-retry-allowed@^1.1.0: resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== +is-self-closing@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-self-closing/-/is-self-closing-1.0.1.tgz#5f406b527c7b12610176320338af0fa3896416e4" + integrity sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg== + dependencies: + self-closing-tags "^1.0.1" + is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -5917,6 +6550,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +isobject@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-0.2.0.tgz#a3432192f39b910b5f02cc989487836ec70aa85e" + integrity sha1-o0MhkvObkQtfAsyYlIeDbscKqF4= + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -6930,6 +7568,22 @@ jsonschema@1.4.0: resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2" integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw== +jsonwebtoken@^8.2.0, jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -6969,7 +7623,7 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jws@3.x.x: +jws@3.x.x, jws@^3.0.0, jws@^3.1.4, jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== @@ -6996,7 +7650,7 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= @@ -7010,12 +7664,12 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" -kind-of@^5.0.0: +kind-of@^5.0.0, kind-of@^5.0.2: version "5.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== -kind-of@^6.0.0, kind-of@^6.0.2: +kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -7094,6 +7748,13 @@ koa-is-json@^1.0.0: resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14" integrity sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ= +koa-passport@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/koa-passport/-/koa-passport-4.1.4.tgz#5f1665c1c2a37ace79af9f970b770885ca30ccfa" + integrity sha512-dJBCkl4X+zdYxbI2V2OtoGy0PUenpvp2ZLLWObc8UJhsId0iQpTFT8RVcuA0709AL2txGwRHnSPoT1bYNGa6Kg== + dependencies: + passport "^0.4.0" + koa-pino-logger@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/koa-pino-logger/-/koa-pino-logger-3.0.0.tgz#27600b4f3639e8767dfc6b66493109c5457f53ba" @@ -7375,6 +8036,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -7390,11 +8056,41 @@ lodash.flatten@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + lodash.keys@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" @@ -7405,11 +8101,21 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.noop@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" + integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw= + lodash.omit@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.pick@^4.0.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" @@ -7420,6 +8126,21 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= +lodash.template@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.without@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" @@ -7430,7 +8151,7 @@ lodash.xor@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.xor/-/lodash.xor-4.5.0.tgz#4d48ed7e98095b0632582ba714d3ff8ae8fb1db6" integrity sha1-TUjtfpgJWwYyWCunFNP/iuj7HbY= -lodash@4.17.21, lodash@4.x, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.7.0: +lodash@4.17.21, lodash@4.x, lodash@^4.14.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -7586,7 +8307,7 @@ methods@^1.0.1, methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^3.1.10, micromatch@^3.1.4: +micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.5: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -7675,6 +8396,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@1.x, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -7694,7 +8420,7 @@ moment-timezone@^0.5.31: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0": +"moment@>= 2.9.0", moment@^2.18.1: version "2.29.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== @@ -7788,6 +8514,11 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nanoid@^2.1.0: + version "2.1.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" + integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -7835,6 +8566,11 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -7855,6 +8591,11 @@ node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-forge@^0.7.1: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== + node-gyp-build@~4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.1.1.tgz#d7270b5d86717068d114cc57fff352f96d745feb" @@ -7966,6 +8707,11 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +oauth@0.9.x: + version "0.9.15" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" + integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= + object-assign@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" @@ -8263,6 +9009,84 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +passport-google-auth@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938" + integrity sha1-izALWqRC70M94dgy7TESh30LKTg= + dependencies: + googleapis "^16.0.0" + passport-strategy "1.x" + +passport-google-oauth1@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc" + integrity sha1-r3SoA99R7GRvZqRNgigr5vEI4Mw= + dependencies: + passport-oauth1 "1.x.x" + +passport-google-oauth20@2.x.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef" + integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ== + dependencies: + passport-oauth2 "1.x.x" + +passport-google-oauth@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/passport-google-oauth/-/passport-google-oauth-2.0.0.tgz#f6eb4bc96dd6c16ec0ecfdf4e05ec48ca54d4dae" + integrity sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA== + dependencies: + passport-google-oauth1 "1.x.x" + passport-google-oauth20 "2.x.x" + +passport-jwt@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.0.tgz#7f0be7ba942e28b9f5d22c2ebbb8ce96ef7cf065" + integrity sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg== + dependencies: + jsonwebtoken "^8.2.0" + passport-strategy "^1.0.0" + +passport-local@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" + integrity sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4= + dependencies: + passport-strategy "1.x.x" + +passport-oauth1@1.x.x: + version "1.2.0" + resolved "https://registry.yarnpkg.com/passport-oauth1/-/passport-oauth1-1.2.0.tgz#5229d431781bf5b265bec86ce9a9cce58a756cf9" + integrity sha512-Sv2YWodC6jN12M/OXwmR4BIXeeIHjjbwYTQw4kS6tHK4zYzSEpxBgSJJnknBjICA5cj0ju3FSnG1XmHgIhYnLg== + dependencies: + oauth "0.9.x" + passport-strategy "1.x.x" + utils-merge "1.x.x" + +passport-oauth2@1.x.x: + version "1.6.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.0.tgz#5f599735e0ea40ea3027643785f81a3a9b4feb50" + integrity sha512-emXPLqLcVEcLFR/QvQXZcwLmfK8e9CqvMgmOFJxcNT3okSFMtUbRRKpY20x5euD+01uHsjjCa07DYboEeLXYiw== + dependencies: + base64url "3.x.x" + oauth "0.9.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + +passport-strategy@1.x, passport-strategy@1.x.x, passport-strategy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= + +passport@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" + integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -8317,6 +9141,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -9019,7 +9848,7 @@ readable-stream@1.1.14, readable-stream@^1.0.27-1: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -9153,6 +9982,16 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexparam@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-2.0.0.tgz#059476767d5f5f87f735fc7922d133fd1a118c8c" + integrity sha512-gJKwd2MVPWHAIFLsaYDZfyKzHNS4o7E/v8YmNf44vmeV2e4YfVoDToTOKTvE7ab68cRJ++kLuEXJBaEeJVt5ow== + +regexparam@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f" + integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g== + regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -9196,6 +10035,21 @@ regjsparser@^0.6.4: dependencies: jsesc "~0.5.0" +relative@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/relative/-/relative-3.0.2.tgz#0dcd8ec54a5d35a3c15e104503d65375b5a5367f" + integrity sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8= + dependencies: + isobject "^2.0.0" + +remarkable@^1.6.2, remarkable@^1.7.1: + version "1.7.4" + resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00" + integrity sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg== + dependencies: + argparse "^1.0.10" + autolinker "~0.28.0" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -9227,7 +10081,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -"request@>= 2.52.0", request@^2.87.0: +"request@>= 2.52.0", request@^2.72.0, request@^2.74.0, request@^2.87.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -9419,6 +10273,11 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" +sanitize-s3-objectkey@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/sanitize-s3-objectkey/-/sanitize-s3-objectkey-0.0.1.tgz#efa9887cd45275b40234fb4bb12fc5754fe64e7e" + integrity sha512-ZTk7aqLxy4sD40GWcYWoLfbe05XLmkKvh6vGKe13ADlei24xlezcvjgKy1qRArlaIbIMYaqK7PCalvZtulZlaQ== + saslprep@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" @@ -9455,6 +10314,11 @@ seek-bzip@^1.0.5: dependencies: commander "^2.8.1" +self-closing-tags@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/self-closing-tags/-/self-closing-tags-1.0.1.tgz#6c5fa497994bb826b484216916371accee490a5d" + integrity sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA== + semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" @@ -9588,6 +10452,13 @@ shell-path@^2.1.0: dependencies: shell-env "^0.3.0" +shortid@^2.2.15: + version "2.2.16" + resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.16.tgz#b742b8f0cb96406fd391c76bfc18a67a57fe5608" + integrity sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g== + dependencies: + nanoid "^2.1.0" + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -9887,6 +10758,11 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +string-template@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96" + integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y= + string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -10012,6 +10888,11 @@ strip-outer@^1.0.0: dependencies: escape-string-regexp "^1.0.2" +striptags@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/striptags/-/striptags-3.2.0.tgz#cc74a137db2de8b0b9a370006334161f7dd67052" + integrity sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw== + sublevel-pouchdb@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/sublevel-pouchdb/-/sublevel-pouchdb-7.2.2.tgz#49e46cd37883bf7ff5006d7c5b9bcc7bcc1f422f" @@ -10082,11 +10963,92 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" +svelte-apexcharts@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/svelte-apexcharts/-/svelte-apexcharts-1.0.2.tgz#4e000f8b8f7c901c05658c845457dfc8314d54c1" + integrity sha512-6qlx4rE+XsonZ0FZudfwqOQ34Pq+3wpxgAD75zgEmGoYhYBJcwmikTuTf3o8ZBsZue9U/pAwhNy3ed1Bkq1gmA== + dependencies: + apexcharts "^3.19.2" + +svelte-flatpickr@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.2.0.tgz#7af5455670aa1836db1c286f5c827d9bae7df74c" + integrity sha512-4JsPP7xFBPpvGNiFxfbYjv+SrGoGSRMvEdY7c+wHz37PM33NLE+IUmz7FQRNaHyVelmrTyAHPY1CNrI7cmTPhA== + dependencies: + flatpickr "^4.5.2" + +svelte-portal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3" + integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q== + +svelte-spa-router@^3.0.5: + version "3.2.0" + resolved "https://registry.yarnpkg.com/svelte-spa-router/-/svelte-spa-router-3.2.0.tgz#fae3311d292451236cb57131262406cf312b15ee" + integrity sha512-igemo5Vs82TGBBw+DjWt6qKameXYzNs6aDXcTxou5XbEvOjiRcAM6MLkdVRCatn6u8r42dE99bt/br7T4qe/AQ== + dependencies: + regexparam "2.0.0" + svelte@^3.38.2: version "3.38.2" resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.38.2.tgz#55e5c681f793ae349b5cc2fe58e5782af4275ef5" integrity sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg== +svg.draggable.js@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba" + integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw== + dependencies: + svg.js "^2.0.1" + +svg.easing.js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/svg.easing.js/-/svg.easing.js-2.0.0.tgz#8aa9946b0a8e27857a5c40a10eba4091e5691f12" + integrity sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI= + dependencies: + svg.js ">=2.3.x" + +svg.filter.js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203" + integrity sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM= + dependencies: + svg.js "^2.2.5" + +svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d" + integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA== + +svg.pathmorphing.js@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65" + integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww== + dependencies: + svg.js "^2.4.0" + +svg.resize.js@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332" + integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw== + dependencies: + svg.js "^2.6.5" + svg.select.js "^2.1.2" + +svg.select.js@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73" + integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ== + dependencies: + svg.js "^2.2.5" + +svg.select.js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917" + integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw== + dependencies: + svg.js "^2.6.5" + symbol-tree@^3.2.2, symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -10102,6 +11064,16 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" +tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + tar-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" @@ -10115,6 +11087,17 @@ tar-stream@^1.5.2: to-buffer "^1.1.1" xtend "^4.0.0" +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tarn@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/tarn/-/tarn-1.1.5.tgz#7be88622e951738b9fa3fb77477309242cdddc2d" @@ -10286,6 +11269,11 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +to-gfm-code-block@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz#25d045a5fae553189e9637b590900da732d8aa82" + integrity sha1-JdBFpfrlUxielje1kJANpzLYqoI= + to-json-schema@0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/to-json-schema/-/to-json-schema-0.2.5.tgz#ef3c3f11ad64460dcfbdbafd0fd525d69d62a98f" @@ -10488,11 +11476,28 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typeof-article@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/typeof-article/-/typeof-article-0.1.1.tgz#9f07e733c3fbb646ffa9e61c08debacd460e06af" + integrity sha1-nwfnM8P7tkb/qeYcCN66zUYOBq8= + dependencies: + kind-of "^3.1.0" + typescript@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.4.tgz#3f85b986945bcf31071decdd96cf8bfa65f9dcbc" integrity sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew== +uglify-js@^3.1.4: + version "3.14.0" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.0.tgz#2d723a0afee81e0d08db9354a9c277006e942386" + integrity sha512-R/tiGB1ZXp2BC+TkRGLwj8xUZgdfT2f4UZEgX6aVjJ5uttPrr4fYmwTWDGqVnBCLbOXRMY6nr/BTbwCtVfps0g== + +uid2@0.0.x: + version "0.0.3" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I= + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -10595,6 +11600,11 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== +update-dotenv@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-dotenv/-/update-dotenv-1.1.1.tgz#17146f302f216c3c92419d5a327a45be910050ca" + integrity sha512-3cIC18In/t0X/yH793c00qqxcKD8jVCgNOPif/fGQkFpYMGecM9YAc+kaAKXuZsM2dE9I9wFI7KvAuNX22SGMQ== + update-notifier@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" @@ -10679,7 +11689,7 @@ util.promisify@^1.0.0, util.promisify@^1.0.1: has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.1" -utils-merge@1.0.1: +utils-merge@1.0.1, utils-merge@1.x.x: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= @@ -10704,7 +11714,7 @@ uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.0: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -10872,6 +11882,11 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + worker-farm@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" @@ -11115,6 +12130,11 @@ yauzl@^2.4.2: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" +year@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" + integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A= + ylru@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f" @@ -11125,7 +12145,7 @@ yn@3.1.1: resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== -zlib@1.0.5: +zlib@1.0.5, zlib@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0" integrity sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA= diff --git a/packages/worker/package.json b/packages/worker/package.json index 373c51716f..c10d96a0ea 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -16,7 +16,9 @@ "build:docker": "docker build . -t worker-service", "dev:stack:init": "node ./scripts/dev/manage.js init", "dev:builder": "npm run dev:stack:init && nodemon src/index.js", - "test": "jest --runInBand" + "test": "jest --runInBand", + "multi:enable": "node scripts/multiTenancy.js enable", + "multi:disable": "node scripts/multiTenancy.js disable" }, "author": "Budibase", "license": "AGPL-3.0-or-later", @@ -52,7 +54,8 @@ "jest": "^26.6.3", "nodemon": "^2.0.7", "pouchdb-adapter-memory": "^7.2.2", - "supertest": "^6.1.3" + "supertest": "^6.1.3", + "update-dotenv": "^1.1.1" }, "jest": { "testEnvironment": "node", diff --git a/packages/worker/scripts/multiTenancy.js b/packages/worker/scripts/multiTenancy.js new file mode 100644 index 0000000000..3921a78979 --- /dev/null +++ b/packages/worker/scripts/multiTenancy.js @@ -0,0 +1,8 @@ +#!/usr/bin/env node +const updateDotEnv = require("update-dotenv") + +const arg = process.argv.slice(2)[0] + +updateDotEnv({ + MULTI_TENANCY: arg === "enable" ? "1" : "", +}).then(() => console.log("Updated worker!")) diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index 1d4227363f..514b850a3f 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -287,6 +287,66 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@budibase/auth@^0.9.79-alpha.4": + version "0.9.79" + resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.79.tgz#416271ffc55e84116550469656bf151a7734a90f" + integrity sha512-ENh099tYeUfVExsAeoxwMh2ODioKQGPteK9LJiU5hMdM4Oi7pyImu287BgKpTIheB+WtadT4e21VpPaJ62APEw== + dependencies: + aws-sdk "^2.901.0" + bcryptjs "^2.4.3" + ioredis "^4.27.1" + jsonwebtoken "^8.5.1" + koa-passport "^4.1.4" + lodash "^4.17.21" + node-fetch "^2.6.1" + passport-google-auth "^1.0.2" + passport-google-oauth "^2.0.0" + passport-jwt "^4.0.0" + passport-local "^1.0.0" + sanitize-s3-objectkey "^0.0.1" + tar-fs "^2.1.1" + uuid "^8.3.2" + zlib "^1.0.5" + +"@budibase/handlebars-helpers@^0.11.4": + version "0.11.5" + resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.5.tgz#e9cc90a44e94ad536992cf10906829b633e94bc5" + integrity sha512-ZxpyNtTHxS8Y+yTicbgWvYDAydooUSjOf3Y+wmTE2d4NpDgO0g0IjepLfZV+KASv9XBr//ylJdjE4hClX9NTFw== + dependencies: + array-sort "^1.0.0" + define-property "^2.0.2" + extend-shallow "^3.0.2" + "falsey" "^1.0.0" + for-in "^1.0.2" + get-object "^0.2.0" + get-value "^3.0.1" + handlebars "^4.7.7" + handlebars-utils "^1.0.6" + has-value "^2.0.2" + helper-date "^1.0.1" + helper-markdown "^1.0.0" + helper-md "^0.2.2" + html-tag "^2.0.0" + is-even "^1.0.0" + is-glob "^4.0.1" + kind-of "^6.0.3" + micromatch "^3.1.5" + relative "^3.0.2" + striptags "^3.1.1" + to-gfm-code-block "^0.1.1" + year "^0.2.1" + +"@budibase/string-templates@^0.9.79-alpha.4": + version "0.9.79" + resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.79.tgz#bb75a7433a7cfda1fc488283f35e47879b799fcc" + integrity sha512-hkAne5mx7mj8+osXFt45VwgLKSa94uQOGOb4R8uv9WNzvk4RzcjBfRzJxggv29FUemItrAeZpSh+Um6yugFI+w== + dependencies: + "@budibase/handlebars-helpers" "^0.11.4" + dayjs "^1.10.4" + handlebars "^4.7.6" + handlebars-utils "^1.0.6" + lodash "^4.17.20" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -879,7 +939,7 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -argparse@^1.0.7: +argparse@^1.0.10, argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== @@ -916,6 +976,15 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= +array-sort@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" + integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== + dependencies: + default-compare "^1.0.0" + get-value "^2.0.6" + kind-of "^5.0.2" + array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -948,6 +1017,13 @@ ast-types@0.9.6: resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk= +async@~2.1.4: + version "2.1.5" + resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc" + integrity sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw= + dependencies: + lodash "^4.14.0" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -963,6 +1039,13 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== +autolinker@~0.28.0: + version "0.28.1" + resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.28.1.tgz#0652b491881879f0775dace0cdca3233942a4e47" + integrity sha1-BlK0kYgYefB3XazgzcoyM5QqTkc= + dependencies: + gulp-header "^1.7.1" + aws-sdk@^2.811.0: version "2.811.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.811.0.tgz#a7e4040b2ee7d8b825b142ed5179d36dc3f315c4" @@ -978,6 +1061,21 @@ aws-sdk@^2.811.0: uuid "3.3.2" xml2js "0.4.19" +aws-sdk@^2.901.0: + version "2.953.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.953.0.tgz#2ba87da084164bcb8e325e67356b8372e54cb5d9" + integrity sha512-CCsJm+ggE1HQ2fkCto+JRqJyET81Vw8eZr/KnNw19jqRiAsXNj5GqAh1JNyTCHEif8PcjREtP7o3y093ylSUyg== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -1104,6 +1202,15 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + boxen@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" @@ -1348,6 +1455,11 @@ chokidar@^3.2.2: optionalDependencies: fsevents "~2.3.1" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -1394,6 +1506,11 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +cluster-key-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" + integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== + co-body@^5.1.1: version "5.2.0" resolved "https://registry.yarnpkg.com/co-body/-/co-body-5.2.0.tgz#5a0a658c46029131e0e3a306f67647302f71c124" @@ -1495,6 +1612,13 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +concat-with-sourcemaps@*: + version "1.1.0" + resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" + integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== + dependencies: + source-map "^0.6.1" + configstore@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" @@ -1614,11 +1738,23 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date.js@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.3.tgz#ef1e92332f507a638795dbb985e951882e50bbda" + integrity sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw== + dependencies: + debug "~3.1.0" + dateformat@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +dayjs@^1.10.4: + version "1.10.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" + integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== + debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -1640,6 +1776,13 @@ debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" +debug@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -1696,6 +1839,13 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +default-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" + integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== + dependencies: + kind-of "^5.0.2" + defer-to-connect@^1.0.1: version "1.1.3" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" @@ -1751,6 +1901,11 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +denque@^1.1.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" + integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== + depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -1868,7 +2023,7 @@ encoding-down@^6.3.0: level-codec "^9.0.0" level-errors "^2.0.0" -end-of-stream@^1.1.0: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -1882,6 +2037,11 @@ end-stream@~0.1.0: dependencies: write-stream "~0.4.3" +ent@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" + integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= + errno@~0.1.1: version "0.1.8" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" @@ -2123,6 +2283,11 @@ falafel@^1.0.1: isarray "0.0.1" object-keys "^1.0.6" +"falsey@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/falsey/-/falsey-1.0.0.tgz#71bdd775c24edad9f2f5c015ce8be24400bb5d7d" + integrity sha512-zMDNZ/Ipd8MY0+346CPvhzP1AsiVyNfTOayJza4reAIWf72xbkuFUDcJNxSAsQE1b9Bu0wijKb8Ngnh/a7fI5w== + fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2249,6 +2414,16 @@ fresh@~0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-exists-sync@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" + integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2288,6 +2463,14 @@ get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.1" +get-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/get-object/-/get-object-0.2.0.tgz#d92ff7d5190c64530cda0543dac63a3d47fe8c0c" + integrity sha1-2S/31RkMZFMM2gVD2sY6PUf+jAw= + dependencies: + is-number "^2.0.2" + isobject "^0.2.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -2312,6 +2495,13 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +get-value@^3.0.0, get-value@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8" + integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA== + dependencies: + isobject "^3.0.1" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -2361,6 +2551,32 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +google-auth-library@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.10.0.tgz#6e15babee85fd1dd14d8d128a295b6838d52136e" + integrity sha1-bhW6vuhf0d0U2NEoopW2g41SE24= + dependencies: + gtoken "^1.2.1" + jws "^3.1.4" + lodash.noop "^3.0.1" + request "^2.74.0" + +google-p12-pem@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-0.1.2.tgz#33c46ab021aa734fa0332b3960a9a3ffcb2f3177" + integrity sha1-M8RqsCGqc0+gMys5YKmj/8svMXc= + dependencies: + node-forge "^0.7.1" + +googleapis@^16.0.0: + version "16.1.0" + resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-16.1.0.tgz#0f19f2d70572d918881a0f626e3b1a2fa8629576" + integrity sha1-Dxny1wVy2RiIGg9ibjsaL6hilXY= + dependencies: + async "~2.1.4" + google-auth-library "~0.10.0" + string-template "~1.0.0" + got@^11.8.1: version "11.8.1" resolved "https://registry.yarnpkg.com/got/-/got-11.8.1.tgz#df04adfaf2e782babb3daabc79139feec2f7e85d" @@ -2410,6 +2626,45 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +gtoken@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.3.tgz#5509571b8afd4322e124cf66cf68115284c476d8" + integrity sha512-wQAJflfoqSgMWrSBk9Fg86q+sd6s7y6uJhIvvIPz++RElGlMtEqsdAR2oWwZ/WTEtp7P9xFbJRrT976oRgzJ/w== + dependencies: + google-p12-pem "^0.1.0" + jws "^3.0.0" + mime "^1.4.1" + request "^2.72.0" + +gulp-header@^1.7.1: + version "1.8.12" + resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.12.tgz#ad306be0066599127281c4f8786660e705080a84" + integrity sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ== + dependencies: + concat-with-sourcemaps "*" + lodash.template "^4.4.0" + through2 "^2.0.0" + +handlebars-utils@^1.0.2, handlebars-utils@^1.0.4, handlebars-utils@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9" + integrity sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw== + dependencies: + kind-of "^6.0.0" + typeof-article "^0.1.1" + +handlebars@^4.7.6, handlebars@^4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -2456,6 +2711,14 @@ has-value@^1.0.0: has-values "^1.0.0" isobject "^3.0.0" +has-value@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-2.0.2.tgz#d0f12e8780ba8e90e66ad1a21c707fdb67c25658" + integrity sha512-ybKOlcRsK2MqrM3Hmz/lQxXHZ6ejzSPzpNabKB45jb5qDgJvKPa3SdapTsTLwEb9WltgWpOmNax7i+DzNOk4TA== + dependencies: + get-value "^3.0.0" + has-values "^2.0.1" + has-values@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" @@ -2469,6 +2732,13 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has-values@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-2.0.1.tgz#3876200ff86d8a8546a9264a952c17d5fc17579d" + integrity sha512-+QdH3jOmq9P8GfdjFg0eJudqx1FqU62NQJ4P16rOEHeRdl7ckgwn6uqQjzYE0ZoHVV/e5E2esuJ5Gl5+HUW19w== + dependencies: + kind-of "^6.0.2" + has-yarn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" @@ -2481,6 +2751,39 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +helper-date@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/helper-date/-/helper-date-1.0.1.tgz#12fedea3ad8e44a7ca4c4efb0ff4104a5120cffb" + integrity sha512-wU3VOwwTJvGr/w5rZr3cprPHO+hIhlblTJHD6aFBrKLuNbf4lAmkawd2iK3c6NbJEvY7HAmDpqjOFSI5/+Ey2w== + dependencies: + date.js "^0.3.1" + handlebars-utils "^1.0.4" + moment "^2.18.1" + +helper-markdown@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/helper-markdown/-/helper-markdown-1.0.0.tgz#ee7e9fc554675007d37eb90f7853b13ce74f3e10" + integrity sha512-AnDqMS4ejkQK0MXze7pA9TM3pu01ZY+XXsES6gEE0RmCGk5/NIfvTn0NmItfyDOjRAzyo9z6X7YHbHX4PzIvOA== + dependencies: + handlebars-utils "^1.0.2" + highlight.js "^9.12.0" + remarkable "^1.7.1" + +helper-md@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/helper-md/-/helper-md-0.2.2.tgz#c1f59d7e55bbae23362fd8a0e971607aec69d41f" + integrity sha1-wfWdflW7riM2L9ig6XFgeuxp1B8= + dependencies: + ent "^2.2.0" + extend-shallow "^2.0.1" + fs-exists-sync "^0.1.0" + remarkable "^1.6.2" + +highlight.js@^9.12.0: + version "9.18.5" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" + integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -2498,6 +2801,14 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-tag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/html-tag/-/html-tag-2.0.0.tgz#36c3bc8d816fd30b570d5764a497a641640c2fed" + integrity sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g== + dependencies: + is-self-closing "^1.0.1" + kind-of "^6.0.0" + http-assert@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.4.1.tgz#c5f725d677aa7e873ef736199b89686cceb37878" @@ -2628,7 +2939,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1: +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2656,6 +2967,22 @@ inline-process-browser@^1.0.0: falafel "^1.0.1" through2 "^0.6.5" +ioredis@^4.27.1: + version "4.27.6" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.27.6.tgz#a53d427d3fe75fbd10ed7ad150ce00559df8dcf8" + integrity sha512-6W3ZHMbpCa8ByMyC1LJGOi7P2WiOKP9B3resoZOVLDhi+6dDBOW+KNsRq3yI36Hmnb2sifCxHX+YSarTeXh48A== + dependencies: + cluster-key-slot "^1.1.0" + debug "^4.3.1" + denque "^1.1.0" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + p-map "^2.1.0" + redis-commands "1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -2743,6 +3070,13 @@ is-docker@^2.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-even@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-even/-/is-even-1.0.0.tgz#76b5055fbad8d294a86b6a949015e1c97b717c06" + integrity sha1-drUFX7rY0pSoa2qUkBXhyXtxfAY= + dependencies: + is-odd "^0.1.2" + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -2800,6 +3134,13 @@ is-npm@^4.0.0: resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== +is-number@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= + dependencies: + kind-of "^3.0.2" + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -2817,6 +3158,13 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== +is-odd@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-0.1.2.tgz#bc573b5ce371ef2aad6e6f49799b72bef13978a7" + integrity sha1-vFc7XONx7yqtbm9JeZtyvvE5eKc= + dependencies: + is-number "^3.0.0" + is-path-inside@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -2834,6 +3182,13 @@ is-potential-custom-element-name@^1.0.0: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== +is-self-closing@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-self-closing/-/is-self-closing-1.0.1.tgz#5f406b527c7b12610176320338af0fa3896416e4" + integrity sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg== + dependencies: + self-closing-tags "^1.0.1" + is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -2880,7 +3235,7 @@ isarray@0.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -isarray@1.0.0, isarray@^1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -2890,6 +3245,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +isobject@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-0.2.0.tgz#a3432192f39b910b5f02cc989487836ec70aa85e" + integrity sha1-o0MhkvObkQtfAsyYlIeDbscKqF4= + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -3434,7 +3794,7 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" -jsonwebtoken@^8.2.0: +jsonwebtoken@^8.2.0, jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== @@ -3489,7 +3849,7 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jws@^3.2.2: +jws@^3.0.0, jws@^3.1.4, jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== @@ -3518,7 +3878,7 @@ keyv@^4.0.0: dependencies: json-buffer "3.0.1" -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= @@ -3532,12 +3892,12 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" -kind-of@^5.0.0: +kind-of@^5.0.0, kind-of@^5.0.2: version "5.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== -kind-of@^6.0.0, kind-of@^6.0.2: +kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -3798,6 +4158,21 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -3828,12 +4203,32 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= +lodash.noop@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" + integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw= + lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash@^4.17.19, lodash@^4.7.0: +lodash.template@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + dependencies: + lodash._reinterpolate "^3.0.0" + +lodash@^4.14.0, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3913,7 +4308,7 @@ methods@^1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^3.1.4: +micromatch@^3.1.4, micromatch@^3.1.5: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -3969,6 +4364,11 @@ mime-types@^2.1.18, mime-types@~2.1.24: dependencies: mime-db "1.44.0" +mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + mime@^2.4.6: version "2.5.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" @@ -4009,6 +4409,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@^0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -4016,6 +4421,11 @@ mkdirp@^0.5.0: dependencies: minimist "^1.2.5" +moment@^2.18.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + mri@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" @@ -4068,6 +4478,11 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -4083,6 +4498,11 @@ node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-forge@^0.7.1: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== + node-gyp-build@~4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.1.1.tgz#d7270b5d86717068d114cc57fff352f96d745feb" @@ -4309,6 +4729,11 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-map@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -4349,6 +4774,14 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +passport-google-auth@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938" + integrity sha1-izALWqRC70M94dgy7TESh30LKTg= + dependencies: + googleapis "^16.0.0" + passport-strategy "1.x" + passport-google-oauth1@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc" @@ -4406,7 +4839,7 @@ passport-oauth2@1.x.x: uid2 "0.0.x" utils-merge "1.x.x" -passport-strategy@1.x.x, passport-strategy@^1.0.0: +passport-strategy@1.x, passport-strategy@1.x.x, passport-strategy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= @@ -4696,6 +5129,11 @@ private@^0.1.6, private@~0.1.5: resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + prompts@^2.0.1: version "2.4.1" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.1.tgz#befd3b1195ba052f9fd2fde8a486c4e82ee77f61" @@ -4840,7 +5278,7 @@ readable-stream@1.1.14: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.4.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -4864,6 +5302,19 @@ readable-stream@~0.0.2: resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-0.0.4.tgz#f32d76e3fb863344a548d79923007173665b3b8d" integrity sha1-8y124/uGM0SlSNeZIwBxc2ZbO40= +readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readdirp@~3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" @@ -4891,6 +5342,23 @@ recast@^0.11.17: private "~0.1.5" source-map "~0.5.0" +redis-commands@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" + integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -4913,6 +5381,21 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" +relative@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/relative/-/relative-3.0.2.tgz#0dcd8ec54a5d35a3c15e104503d65375b5a5367f" + integrity sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8= + dependencies: + isobject "^2.0.0" + +remarkable@^1.6.2, remarkable@^1.7.1: + version "1.7.4" + resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00" + integrity sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg== + dependencies: + argparse "^1.0.10" + autolinker "~0.28.0" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -4944,7 +5427,7 @@ request-promise-native@^1.0.9: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.88.0, request@^2.88.2: +request@^2.72.0, request@^2.74.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -5049,7 +5532,7 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== -safe-buffer@5.1.2, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -5086,6 +5569,11 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" +sanitize-s3-objectkey@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/sanitize-s3-objectkey/-/sanitize-s3-objectkey-0.0.1.tgz#efa9887cd45275b40234fb4bb12fc5754fe64e7e" + integrity sha512-ZTk7aqLxy4sD40GWcYWoLfbe05XLmkKvh6vGKe13ADlei24xlezcvjgKy1qRArlaIbIMYaqK7PCalvZtulZlaQ== + sax@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" @@ -5103,6 +5591,11 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" +self-closing-tags@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/self-closing-tags/-/self-closing-tags-1.0.1.tgz#6c5fa497994bb826b484216916371accee490a5d" + integrity sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA== + semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" @@ -5378,6 +5871,11 @@ stack-utils@^2.0.2: dependencies: escape-string-regexp "^2.0.0" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -5414,6 +5912,11 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +string-template@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96" + integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y= + string-width@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -5444,6 +5947,13 @@ string_decoder@~0.10.x: resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -5483,6 +5993,11 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +striptags@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/striptags/-/striptags-3.2.0.tgz#cc74a137db2de8b0b9a370006334161f7dd67052" + integrity sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw== + sublevel-pouchdb@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/sublevel-pouchdb/-/sublevel-pouchdb-7.2.2.tgz#49e46cd37883bf7ff5006d7c5b9bcc7bcc1f422f" @@ -5545,6 +6060,27 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + term-size@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" @@ -5588,6 +6124,14 @@ through2@^0.6.2, through2@^0.6.5: readable-stream ">=1.0.33-1 <1.1.0-0" xtend ">=4.0.0 <4.1.0-0" +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -5608,6 +6152,11 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +to-gfm-code-block@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz#25d045a5fae553189e9637b590900da732d8aa82" + integrity sha1-JdBFpfrlUxielje1kJANpzLYqoI= + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -5740,6 +6289,18 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typeof-article@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/typeof-article/-/typeof-article-0.1.1.tgz#9f07e733c3fbb646ffa9e61c08debacd460e06af" + integrity sha1-nwfnM8P7tkb/qeYcCN66zUYOBq8= + dependencies: + kind-of "^3.1.0" + +uglify-js@^3.1.4: + version "3.14.0" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.0.tgz#2d723a0afee81e0d08db9354a9c277006e942386" + integrity sha512-R/tiGB1ZXp2BC+TkRGLwj8xUZgdfT2f4UZEgX6aVjJ5uttPrr4fYmwTWDGqVnBCLbOXRMY6nr/BTbwCtVfps0g== + uid2@0.0.x: version "0.0.3" resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" @@ -5796,6 +6357,11 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +update-dotenv@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-dotenv/-/update-dotenv-1.1.1.tgz#17146f302f216c3c92419d5a327a45be910050ca" + integrity sha512-3cIC18In/t0X/yH793c00qqxcKD8jVCgNOPif/fGQkFpYMGecM9YAc+kaAKXuZsM2dE9I9wFI7KvAuNX22SGMQ== + update-notifier@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" @@ -5852,7 +6418,7 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@^1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -5877,7 +6443,7 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.0: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -6009,6 +6575,11 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -6080,7 +6651,7 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.2, xtend@~4.0.0: +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== @@ -6120,7 +6691,17 @@ yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +year@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" + integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A= + ylru@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f" integrity sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ== + +zlib@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0" + integrity sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA= From 615053f2011a148059336afe89a03e24c14473e0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 27 Jul 2021 15:35:26 +0100 Subject: [PATCH 41/56] Fixing some issues with org not being correctly detected. --- packages/auth/src/middleware/authenticated.js | 5 ++- .../builder/src/pages/builder/_layout.svelte | 4 ++ packages/server/scripts/dev/manage.js | 42 ++++++++++--------- .../src/api/controllers/global/configs.js | 6 +-- 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index c79725233f..e3089efb1e 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -33,6 +33,7 @@ function finalise(ctx, { authenticated, user, internal, version } = {}) { module.exports = (noAuthPatterns = [], opts) => { const noAuthOptions = noAuthPatterns ? buildNoAuthRegex(noAuthPatterns) : [] return async (ctx, next) => { + let publicEndpoint = false const version = ctx.request.headers[Headers.API_VER] // the path is not authenticated const found = noAuthOptions.find(({ regex, method }) => { @@ -42,7 +43,7 @@ module.exports = (noAuthPatterns = [], opts) => { ) }) if (found != null) { - return next() + publicEndpoint = true } try { // check the actual user is authenticated first @@ -93,7 +94,7 @@ module.exports = (noAuthPatterns = [], opts) => { return next() } catch (err) { // allow configuring for public access - if (opts && opts.publicAllowed) { + if ((opts && opts.publicAllowed) || publicEndpoint) { finalise(ctx, { authenticated: false, version }) } else { ctx.throw(err.status || 403, err) diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index ce883ee541..6bef986f04 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -16,9 +16,13 @@ // Force creation of an admin user if one doesn't exist $: { + console.log(`loaded: ${loaded}`) + console.log(`tenancy: ${multiTenancyEnabled}`) + console.log(`tenant set: ${tenantSet}`) if (loaded && multiTenancyEnabled && !tenantSet) { $redirect("./auth/org") } else if (loaded && !hasAdminUser) { + $redirect("./admin") } } diff --git a/packages/server/scripts/dev/manage.js b/packages/server/scripts/dev/manage.js index c24d0057f0..e0801bc9df 100644 --- a/packages/server/scripts/dev/manage.js +++ b/packages/server/scripts/dev/manage.js @@ -33,27 +33,29 @@ async function init() { fs.writeFileSync(envoyOutputPath, processStringSync(contents, config)) const envFilePath = path.join(process.cwd(), ".env") - const envFileJson = { - PORT: 4001, - MINIO_URL: "http://localhost:10000/", - COUCH_DB_URL: "http://budibase:budibase@localhost:10000/db/", - REDIS_URL: "localhost:6379", - WORKER_URL: "http://localhost:4002", - INTERNAL_API_KEY: "budibase", - JWT_SECRET: "testsecret", - REDIS_PASSWORD: "budibase", - MINIO_ACCESS_KEY: "budibase", - MINIO_SECRET_KEY: "budibase", - COUCH_DB_PASSWORD: "budibase", - COUCH_DB_USER: "budibase", - SELF_HOSTED: 1, - MULTI_TENANCY: "", + if (!fs.existsSync(envFilePath)) { + const envFileJson = { + PORT: 4001, + MINIO_URL: "http://localhost:10000/", + COUCH_DB_URL: "http://budibase:budibase@localhost:10000/db/", + REDIS_URL: "localhost:6379", + WORKER_URL: "http://localhost:4002", + INTERNAL_API_KEY: "budibase", + JWT_SECRET: "testsecret", + REDIS_PASSWORD: "budibase", + MINIO_ACCESS_KEY: "budibase", + MINIO_SECRET_KEY: "budibase", + COUCH_DB_PASSWORD: "budibase", + COUCH_DB_USER: "budibase", + SELF_HOSTED: 1, + MULTI_TENANCY: "", + } + let envFile = "" + Object.keys(envFileJson).forEach(key => { + envFile += `${key}=${envFileJson[key]}\n` + }) + fs.writeFileSync(envFilePath, envFile) } - let envFile = "" - Object.keys(envFileJson).forEach(key => { - envFile += `${key}=${envFileJson[key]}\n` - }) - fs.writeFileSync(envFilePath, envFile) } async function up() { diff --git a/packages/worker/src/api/controllers/global/configs.js b/packages/worker/src/api/controllers/global/configs.js index 1eb2064b8a..ca066e1492 100644 --- a/packages/worker/src/api/controllers/global/configs.js +++ b/packages/worker/src/api/controllers/global/configs.js @@ -4,7 +4,7 @@ const { getGlobalUserParams, getScopedFullConfig, getGlobalDBFromCtx, - getGlobalDB, + getTenantIdFromCtx, getAllApps, } = require("@budibase/auth/db") const { Configs } = require("../../../constants") @@ -218,8 +218,8 @@ exports.destroy = async function (ctx) { } exports.configChecklist = async function (ctx) { - const tenantId = ctx.request.query.tenantId - const db = tenantId ? getGlobalDB(tenantId) : getGlobalDBFromCtx(ctx) + const tenantId = getTenantIdFromCtx(ctx) + const db = getGlobalDBFromCtx(ctx) try { // TODO: Watch get started video From adf6d18cb1d4b7a0e07cb2f4d67e4244f98cb2b9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 27 Jul 2021 16:17:02 +0100 Subject: [PATCH 42/56] Fixing some issues with public endpoints causing logout loop. --- packages/auth/src/middleware/authenticated.js | 7 ++++--- packages/builder/src/pages/builder/_layout.svelte | 3 --- packages/worker/src/api/index.js | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index e3089efb1e..250b151438 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -23,7 +23,8 @@ function buildNoAuthRegex(patterns) { }) } -function finalise(ctx, { authenticated, user, internal, version } = {}) { +function finalise(ctx, { authenticated, user, internal, version, publicEndpoint } = {}) { + ctx.publicEndpoint = publicEndpoint || false ctx.isAuthenticated = authenticated || false ctx.user = user ctx.internal = internal || false @@ -90,12 +91,12 @@ module.exports = (noAuthPatterns = [], opts) => { authenticated = false } // isAuthenticated is a function, so use a variable to be able to check authed state - finalise(ctx, { authenticated, user, internal, version }) + finalise(ctx, { authenticated, user, internal, version, publicEndpoint }) return next() } catch (err) { // allow configuring for public access if ((opts && opts.publicAllowed) || publicEndpoint) { - finalise(ctx, { authenticated: false, version }) + finalise(ctx, { authenticated: false, version, publicEndpoint }) } else { ctx.throw(err.status || 403, err) } diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index 6bef986f04..b16ffed7a8 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -16,9 +16,6 @@ // Force creation of an admin user if one doesn't exist $: { - console.log(`loaded: ${loaded}`) - console.log(`tenancy: ${multiTenancyEnabled}`) - console.log(`tenant set: ${tenantSet}`) if (loaded && multiTenancyEnabled && !tenantSet) { $redirect("./auth/org") } else if (loaded && !hasAdminUser) { diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js index cfeb3506e5..2e65dc17e7 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -56,7 +56,7 @@ router .use(buildAuthMiddleware(PUBLIC_ENDPOINTS)) // for now no public access is allowed to worker (bar health check) .use((ctx, next) => { - if (!ctx.isAuthenticated) { + if (!ctx.isAuthenticated && !ctx.publicEndpoint) { ctx.throw(403, "Unauthorized - no public worker access") } return next() From 5324f506b66df95c0080a27395bce02bebc350eb Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 27 Jul 2021 16:24:39 +0100 Subject: [PATCH 43/56] Linting. --- packages/auth/src/middleware/authenticated.js | 5 ++++- packages/builder/src/pages/builder/_layout.svelte | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index 250b151438..fa28ca7ba5 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -23,7 +23,10 @@ function buildNoAuthRegex(patterns) { }) } -function finalise(ctx, { authenticated, user, internal, version, publicEndpoint } = {}) { +function finalise( + ctx, + { authenticated, user, internal, version, publicEndpoint } = {} +) { ctx.publicEndpoint = publicEndpoint || false ctx.isAuthenticated = authenticated || false ctx.user = user diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index b16ffed7a8..ce883ee541 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -19,7 +19,6 @@ if (loaded && multiTenancyEnabled && !tenantSet) { $redirect("./auth/org") } else if (loaded && !hasAdminUser) { - $redirect("./admin") } } From 35f1b50511de65078595a857edf3fa5436e25932 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 27 Jul 2021 17:56:39 +0100 Subject: [PATCH 44/56] Fixing an issue where you could accidentally end up on org/admin page due to the reactivity statements firing all the time. --- packages/auth/src/db/utils.js | 6 +++++- packages/auth/src/environment.js | 1 + packages/auth/src/utils.js | 3 ++- packages/builder/src/pages/builder/_layout.svelte | 5 +++-- .../builder/src/pages/builder/auth/org.svelte | 2 ++ packages/builder/src/stores/portal/admin.js | 15 +++++++++++++-- packages/builder/src/stores/portal/auth.js | 3 +++ 7 files changed, 29 insertions(+), 6 deletions(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 259f24406c..1eaf9ddfb6 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -2,6 +2,7 @@ const { newid } = require("../hashing") const Replication = require("./Replication") const { getDB } = require("./index") const { DEFAULT_TENANT_ID } = require("../constants") +const env = require("../environment") const UNICODE_MAX = "\ufff0" const SEPARATOR = "_" @@ -82,6 +83,9 @@ exports.getGlobalDB = tenantId => { if (tenantId && tenantId !== DEFAULT_TENANT_ID) { dbName = `${tenantId}${SEPARATOR}${dbName}` } + if (env.MULTI_TENANCY && tenantId == null) { + throw "Cannot create global DB without tenantId" + } return getDB(dbName) } @@ -210,7 +214,7 @@ exports.getDeployedAppID = appId => { * @return {Promise} returns the app information document stored in each app database. */ exports.getAllApps = async (CouchDB, { tenantId, dev, all } = {}) => { - if (!tenantId) { + if (!env.MULTI_TENANCY && !tenantId) { tenantId = DEFAULT_TENANT_ID } let allDbs = await CouchDB.allDbs() diff --git a/packages/auth/src/environment.js b/packages/auth/src/environment.js index 355843d02d..e12918f3ac 100644 --- a/packages/auth/src/environment.js +++ b/packages/auth/src/environment.js @@ -16,6 +16,7 @@ module.exports = { MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, MINIO_URL: process.env.MINIO_URL, INTERNAL_API_KEY: process.env.INTERNAL_API_KEY, + MULTI_TENANCY: process.env.MULTI_TENANCY, isTest, _set(key, value) { process.env[key] = value diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index 00a3b55a21..dd03f132a1 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -10,6 +10,7 @@ const { createUserEmailView } = require("./db/views") const { getDB } = require("./db") const { getGlobalDB } = require("./db/utils") const { DEFAULT_TENANT_ID, Headers } = require("./constants") +const env = require("./environment") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -104,7 +105,7 @@ exports.isClient = ctx => { exports.lookupTenantId = async userId => { const db = getDB(StaticDatabases.PLATFORM_INFO.name) - let tenantId = DEFAULT_TENANT_ID + let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null try { const doc = await db.get(userId) if (doc && doc.tenantId) { diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index ce883ee541..9ea21317a7 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -16,9 +16,10 @@ // Force creation of an admin user if one doesn't exist $: { - if (loaded && multiTenancyEnabled && !tenantSet) { + const apiReady = $admin.loaded && $auth.loaded + if (loaded && apiReady && multiTenancyEnabled && !tenantSet) { $redirect("./auth/org") - } else if (loaded && !hasAdminUser) { + } else if (loaded && apiReady && !hasAdminUser) { $redirect("./admin") } } diff --git a/packages/builder/src/pages/builder/auth/org.svelte b/packages/builder/src/pages/builder/auth/org.svelte index 0e0fd27c78..7e26924423 100644 --- a/packages/builder/src/pages/builder/auth/org.svelte +++ b/packages/builder/src/pages/builder/auth/org.svelte @@ -27,6 +27,8 @@ auth.checkQueryString() if (!multiTenancyEnabled) { $goto("../") + } else { + admin.unload() } }) diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index 96e67ea84b..cec869eb02 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -3,7 +3,9 @@ import api from "builderStore/api" import { auth } from "stores/portal" export function createAdminStore() { - const admin = writable({}) + const admin = writable({ + loaded: false, + }) async function init() { try { @@ -20,13 +22,14 @@ export function createAdminStore() { 0 ) + await multiTenancyEnabled() admin.update(store => { + store.loaded = true store.checklist = json store.onboardingProgress = (stepsComplete / onboardingSteps.length) * 100 return store }) - await multiTenancyEnabled() } catch (err) { admin.update(store => { store.checklist = null @@ -51,9 +54,17 @@ export function createAdminStore() { return enabled } + function unload() { + admin.update(store => { + store.loaded = false + return store + }) + } + return { subscribe: admin.subscribe, init, + unload, } } diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index aa856d4a29..b8eec0730e 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -6,6 +6,7 @@ export function createAuthStore() { user: null, tenantId: "default", tenantSet: false, + loaded: false, }) const store = derived(auth, $store => { let initials = null @@ -30,6 +31,7 @@ export function createAuthStore() { user: $store.user, tenantId: $store.tenantId, tenantSet: $store.tenantSet, + loaded: $store.loaded, initials, isAdmin, isBuilder, @@ -38,6 +40,7 @@ export function createAuthStore() { function setUser(user) { auth.update(store => { + store.loaded = true store.user = user if (user) { store.tenantId = user.tenantId || "default" From 5dfeb9b3ca6de99fec45b5ca30100d4c0f67617b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 27 Jul 2021 18:02:59 +0100 Subject: [PATCH 45/56] Limiting use of query string to a few select endpoints for determining tenant ID. --- packages/auth/src/db/utils.js | 11 +++++++---- packages/worker/src/api/controllers/global/configs.js | 9 +++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 1eaf9ddfb6..9b6f8fcbed 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -92,21 +92,24 @@ exports.getGlobalDB = tenantId => { /** * Given a koa context this tries to extra what tenant is being accessed. */ -exports.getTenantIdFromCtx = ctx => { +exports.getTenantIdFromCtx = (ctx, opts = { includeQuery: false }) => { if (!ctx) { return null } const user = ctx.user || {} const params = ctx.request.params || {} - const query = ctx.request.query || {} + let query = {} + if (opts && opts.includeQuery) { + query = ctx.request.query || {} + } 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) +exports.getGlobalDBFromCtx = (ctx, opts) => { + const tenantId = exports.getTenantIdFromCtx(ctx, opts) return exports.getGlobalDB(tenantId) } diff --git a/packages/worker/src/api/controllers/global/configs.js b/packages/worker/src/api/controllers/global/configs.js index ca066e1492..000ce85381 100644 --- a/packages/worker/src/api/controllers/global/configs.js +++ b/packages/worker/src/api/controllers/global/configs.js @@ -99,7 +99,7 @@ exports.find = async function (ctx) { } exports.publicOidc = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDBFromCtx(ctx, { includeQuery: true }) try { // Find the config with the most granular scope based on context const oidcConfig = await getScopedFullConfig(db, { @@ -121,7 +121,7 @@ exports.publicOidc = async function (ctx) { } exports.publicSettings = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDBFromCtx(ctx, { includeQuery: true }) try { // Find the config with the most granular scope based on context @@ -218,8 +218,9 @@ exports.destroy = async function (ctx) { } exports.configChecklist = async function (ctx) { - const tenantId = getTenantIdFromCtx(ctx) - const db = getGlobalDBFromCtx(ctx) + // include the query string only for a select few endpoints + const tenantId = getTenantIdFromCtx(ctx, { includeQuery: true }) + const db = getGlobalDBFromCtx(ctx, { includeQuery: true }) try { // TODO: Watch get started video From 79c5f012674db7763236ca03eab772ff4106ac68 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 30 Jul 2021 14:23:40 +0100 Subject: [PATCH 46/56] Fixing config test case. --- packages/worker/src/api/routes/tests/configs.spec.js | 4 +--- .../src/api/routes/tests/utilities/TestConfiguration.js | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/worker/src/api/routes/tests/configs.spec.js b/packages/worker/src/api/routes/tests/configs.spec.js index 285cddec43..a5c2479b02 100644 --- a/packages/worker/src/api/routes/tests/configs.spec.js +++ b/packages/worker/src/api/routes/tests/configs.spec.js @@ -13,14 +13,12 @@ describe("/api/global/configs/checklist", () => { let config = setup.getConfig() beforeAll(async () => { - await config.init(false) + await config.init() }) afterAll(setup.afterAll) it("should return the correct checklist status based on the state of the budibase installation", async () => { - // initially configure settings - await config.saveAdminUser() await config.saveSmtpConfig() const res = await request diff --git a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js index 26023b02f2..3bc9c397d3 100644 --- a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js @@ -62,11 +62,11 @@ class TestConfiguration { null, controllers.users.save ) - await createASession("us_uuid1", { - sessionId: "sessionid", - tenantId: TENANT_ID, - }) } + await createASession("us_uuid1", { + sessionId: "sessionid", + tenantId: TENANT_ID, + }) } async end() { From 8bcce17cd725657feb93b77fb8304211384624b8 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 30 Jul 2021 14:25:38 +0100 Subject: [PATCH 47/56] Removing unused mock. --- packages/worker/src/api/routes/tests/configs.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/worker/src/api/routes/tests/configs.spec.js b/packages/worker/src/api/routes/tests/configs.spec.js index a5c2479b02..10feb77b37 100644 --- a/packages/worker/src/api/routes/tests/configs.spec.js +++ b/packages/worker/src/api/routes/tests/configs.spec.js @@ -1,7 +1,6 @@ const setup = require("./utilities") // mock the email system -const sendMailMock = jest.fn() jest.mock("nodemailer") const nodemailer = require("nodemailer") nodemailer.createTransport.mockReturnValue({ From 61de20616f1dc1e49986b47d87ba06bc39452af9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 30 Jul 2021 15:40:39 +0100 Subject: [PATCH 48/56] Fixing worker dev script to not overwrite env file everytime. --- packages/worker/scripts/dev/manage.js | 40 ++++++++++++++------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/packages/worker/scripts/dev/manage.js b/packages/worker/scripts/dev/manage.js index 682c28feae..281c677ed7 100644 --- a/packages/worker/scripts/dev/manage.js +++ b/packages/worker/scripts/dev/manage.js @@ -4,26 +4,28 @@ const fs = require("fs") async function init() { const envFilePath = path.join(process.cwd(), ".env") - const envFileJson = { - SELF_HOSTED: 1, - PORT: 4002, - CLUSTER_PORT: 10000, - JWT_SECRET: "testsecret", - INTERNAL_API_KEY: "budibase", - MINIO_ACCESS_KEY: "budibase", - MINIO_SECRET_KEY: "budibase", - REDIS_URL: "localhost:6379", - REDIS_PASSWORD: "budibase", - MINIO_URL: "http://localhost:10000/", - COUCH_DB_URL: "http://budibase:budibase@localhost:10000/db/", - // empty string is false - MULTI_TENANCY: "", + if (!fs.existsSync(envFilePath)) { + const envFileJson = { + SELF_HOSTED: 1, + PORT: 4002, + CLUSTER_PORT: 10000, + JWT_SECRET: "testsecret", + INTERNAL_API_KEY: "budibase", + MINIO_ACCESS_KEY: "budibase", + MINIO_SECRET_KEY: "budibase", + REDIS_URL: "localhost:6379", + REDIS_PASSWORD: "budibase", + MINIO_URL: "http://localhost:10000/", + COUCH_DB_URL: "http://budibase:budibase@localhost:10000/db/", + // empty string is false + MULTI_TENANCY: "", + } + let envFile = "" + Object.keys(envFileJson).forEach(key => { + envFile += `${key}=${envFileJson[key]}\n` + }) + fs.writeFileSync(envFilePath, envFile) } - let envFile = "" - Object.keys(envFileJson).forEach(key => { - envFile += `${key}=${envFileJson[key]}\n` - }) - fs.writeFileSync(envFilePath, envFile) } // if more than init required use this to determine the command type From af9b0a738d358f9a87e051957d02f7462b03fbc4 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 30 Jul 2021 20:39:42 +0100 Subject: [PATCH 49/56] Fixing issues with pages reloading in weird ways. --- .../builder/src/pages/builder/_layout.svelte | 7 +++++-- .../builder/src/pages/builder/admin/index.svelte | 10 ++++------ .../builder/src/pages/builder/auth/index.svelte | 2 +- .../builder/src/pages/builder/auth/login.svelte | 5 ++++- .../builder/src/pages/builder/auth/org.svelte | 6 +++--- packages/builder/src/pages/index.svelte | 5 ++++- packages/builder/src/stores/portal/auth.js | 16 +++++++++++----- 7 files changed, 32 insertions(+), 19 deletions(-) diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index 9ea21317a7..b1f6d7e733 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -4,6 +4,7 @@ import { onMount } from "svelte" let loaded = false + $: multiTenancyEnabled = $admin.multiTenancy $: hasAdminUser = !!$admin?.checklist?.adminUser $: tenantSet = $auth.tenantSet @@ -14,12 +15,14 @@ loaded = true }) - // Force creation of an admin user if one doesn't exist $: { const apiReady = $admin.loaded && $auth.loaded + // if tenant is not set go to it if (loaded && apiReady && multiTenancyEnabled && !tenantSet) { $redirect("./auth/org") - } else if (loaded && apiReady && !hasAdminUser) { + } + // Force creation of an admin user if one doesn't exist + else if (loaded && apiReady && !hasAdminUser) { $redirect("./admin") } } diff --git a/packages/builder/src/pages/builder/admin/index.svelte b/packages/builder/src/pages/builder/admin/index.svelte index abacb86b8d..bc76c69cc0 100644 --- a/packages/builder/src/pages/builder/admin/index.svelte +++ b/packages/builder/src/pages/builder/admin/index.svelte @@ -36,11 +36,6 @@ notifications.error(`Failed to create admin user`) } } - - function changeOrg() { - auth.setOrg(null) - $goto("../auth") - }
@@ -62,7 +57,10 @@ Create super admin user {#if multiTenancyEnabled} - + { + admin.unload() + $goto("../auth/org") + }}> Change organisation {/if} diff --git a/packages/builder/src/pages/builder/auth/index.svelte b/packages/builder/src/pages/builder/auth/index.svelte index daeb56cb6e..a2a02e65c1 100644 --- a/packages/builder/src/pages/builder/auth/index.svelte +++ b/packages/builder/src/pages/builder/auth/index.svelte @@ -18,7 +18,7 @@ onMount(async () => { await admin.init() - auth.checkQueryString() + await auth.checkQueryString() loaded = true }) diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte index 2dc3781e30..36632a4862 100644 --- a/packages/builder/src/pages/builder/auth/login.svelte +++ b/packages/builder/src/pages/builder/auth/login.svelte @@ -84,7 +84,10 @@ Forgot password? {#if multiTenancyEnabled} - $goto("./org")}> + { + admin.unload() + $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 7e26924423..785cf05914 100644 --- a/packages/builder/src/pages/builder/auth/org.svelte +++ b/packages/builder/src/pages/builder/auth/org.svelte @@ -13,7 +13,7 @@ if (tenantId == null || tenantId === "") { tenantId = "default" } - auth.setOrg(tenantId) + await auth.setOrg(tenantId) // re-init now org selected await admin.init() $goto("../") @@ -23,8 +23,8 @@ if (evt.key === "Enter") setOrg() } - onMount(() => { - auth.checkQueryString() + onMount(async () => { + await auth.checkQueryString() if (!multiTenancyEnabled) { $goto("../") } else { diff --git a/packages/builder/src/pages/index.svelte b/packages/builder/src/pages/index.svelte index 0c083391ad..477097f726 100644 --- a/packages/builder/src/pages/index.svelte +++ b/packages/builder/src/pages/index.svelte @@ -1,8 +1,11 @@ diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index b8eec0730e..fe8f87cfb2 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -1,5 +1,6 @@ import { derived, writable, get } from "svelte/store" import api from "../../builderStore/api" +import { admin } from "stores/portal" export function createAuthStore() { const auth = writable({ @@ -50,25 +51,30 @@ export function createAuthStore() { }) } - function setOrganisation(tenantId) { + async function setOrganisation(tenantId) { + const prevId = get(store).tenantId auth.update(store => { store.tenantId = tenantId store.tenantSet = !!tenantId return store }) + if (prevId !== tenantId) { + // re-init admin after setting org + await admin.init() + } } return { subscribe: store.subscribe, - checkQueryString: () => { + checkQueryString: async () => { const urlParams = new URLSearchParams(window.location.search) if (urlParams.has("tenantId")) { const tenantId = urlParams.get("tenantId") - setOrganisation(tenantId) + await setOrganisation(tenantId) } }, - setOrg: tenantId => { - setOrganisation(tenantId) + setOrg: async tenantId => { + await setOrganisation(tenantId) }, checkAuth: async () => { const response = await api.get("/api/global/users/self") From 10bb31afd27b711cd2812ab80c281ad99e0cbde9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 30 Jul 2021 20:40:38 +0100 Subject: [PATCH 50/56] Linting. --- packages/builder/src/pages/builder/admin/index.svelte | 11 +++++++---- packages/builder/src/pages/builder/auth/login.svelte | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/builder/src/pages/builder/admin/index.svelte b/packages/builder/src/pages/builder/admin/index.svelte index bc76c69cc0..4d7e39db81 100644 --- a/packages/builder/src/pages/builder/admin/index.svelte +++ b/packages/builder/src/pages/builder/admin/index.svelte @@ -57,10 +57,13 @@ Create super admin user {#if multiTenancyEnabled} - { - admin.unload() - $goto("../auth/org") - }}> + { + admin.unload() + $goto("../auth/org") + }} + > Change organisation {/if} diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte index 36632a4862..783e5a4903 100644 --- a/packages/builder/src/pages/builder/auth/login.svelte +++ b/packages/builder/src/pages/builder/auth/login.svelte @@ -84,10 +84,13 @@ Forgot password? {#if multiTenancyEnabled} - { - admin.unload() - $goto("./org") - }}> + { + admin.unload() + $goto("./org") + }} + > Change organisation {/if} From f6d0db4c4b607fc5084e54e2c7242669ae3e4533 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 2 Aug 2021 12:09:03 +0100 Subject: [PATCH 51/56] Updating API endpoints, to include the new system one, updating hosting envoy yaml to cover these. --- hosting/envoy.dev.yaml.hbs | 8 ++++++++ hosting/envoy.yaml | 10 +++++++++- packages/builder/src/stores/portal/admin.js | 2 +- .../src/api/controllers/{global => system}/flags.js | 0 .../src/api/controllers/{global => system}/tenants.js | 0 packages/worker/src/api/routes/global/flags.js | 8 -------- packages/worker/src/api/routes/global/tenants.js | 11 ----------- packages/worker/src/api/routes/index.js | 4 ++-- packages/worker/src/api/routes/system/flags.js | 8 ++++++++ packages/worker/src/api/routes/system/tenants.js | 11 +++++++++++ 10 files changed, 39 insertions(+), 23 deletions(-) rename packages/worker/src/api/controllers/{global => system}/flags.js (100%) rename packages/worker/src/api/controllers/{global => system}/tenants.js (100%) delete mode 100644 packages/worker/src/api/routes/global/flags.js delete mode 100644 packages/worker/src/api/routes/global/tenants.js create mode 100644 packages/worker/src/api/routes/system/flags.js create mode 100644 packages/worker/src/api/routes/system/tenants.js diff --git a/hosting/envoy.dev.yaml.hbs b/hosting/envoy.dev.yaml.hbs index 5e876a2369..01d5a09efa 100644 --- a/hosting/envoy.dev.yaml.hbs +++ b/hosting/envoy.dev.yaml.hbs @@ -26,6 +26,14 @@ static_resources: cluster: couchdb-service prefix_rewrite: "/" + - match: { prefix: "/api/system/" } + route: + cluster: worker-dev + + - match: { prefix: "/api/admin/" } + route: + cluster: worker-dev + - match: { prefix: "/api/global/" } route: cluster: worker-dev diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index dc90f11056..d5f9ebee28 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -37,11 +37,19 @@ static_resources: route: cluster: app-service - # special case for worker admin API + # special cases for worker admin (deprecated), global and system API - match: { prefix: "/api/global/" } route: cluster: worker-service + - match: { prefix: "/api/admin/" } + route: + cluster: worker-service + + - match: { prefix: "/api/system/" } + route: + cluster: worker-service + - match: { path: "/" } route: cluster: app-service diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index cec869eb02..0699daf8dc 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -41,7 +41,7 @@ export function createAdminStore() { async function multiTenancyEnabled() { let enabled = false try { - const response = await api.get(`/api/global/flags`) + const response = await api.get(`/api/system/flags`) const json = await response.json() enabled = json.multiTenancy } catch (err) { diff --git a/packages/worker/src/api/controllers/global/flags.js b/packages/worker/src/api/controllers/system/flags.js similarity index 100% rename from packages/worker/src/api/controllers/global/flags.js rename to packages/worker/src/api/controllers/system/flags.js diff --git a/packages/worker/src/api/controllers/global/tenants.js b/packages/worker/src/api/controllers/system/tenants.js similarity index 100% rename from packages/worker/src/api/controllers/global/tenants.js rename to packages/worker/src/api/controllers/system/tenants.js diff --git a/packages/worker/src/api/routes/global/flags.js b/packages/worker/src/api/routes/global/flags.js deleted file mode 100644 index a1367f4a82..0000000000 --- a/packages/worker/src/api/routes/global/flags.js +++ /dev/null @@ -1,8 +0,0 @@ -const Router = require("@koa/router") -const controller = require("../../controllers/global/flags") - -const router = Router() - -router.get("/api/global/flags", controller.fetch) - -module.exports = router diff --git a/packages/worker/src/api/routes/global/tenants.js b/packages/worker/src/api/routes/global/tenants.js deleted file mode 100644 index 432e327115..0000000000 --- a/packages/worker/src/api/routes/global/tenants.js +++ /dev/null @@ -1,11 +0,0 @@ -const Router = require("@koa/router") -const controller = require("../../controllers/global/tenants") -const adminOnly = require("../../../middleware/adminOnly") - -const router = Router() - -router - .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/index.js b/packages/worker/src/api/routes/index.js index 6341dffeb7..a4ed4d7da4 100644 --- a/packages/worker/src/api/routes/index.js +++ b/packages/worker/src/api/routes/index.js @@ -2,12 +2,12 @@ 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 flagRoutes = require("./global/flags") +const flagRoutes = require("./system/flags") +const tenantsRoutes = require("./system/tenants") const appRoutes = require("./app") exports.routes = [ diff --git a/packages/worker/src/api/routes/system/flags.js b/packages/worker/src/api/routes/system/flags.js new file mode 100644 index 0000000000..f2f5c5712f --- /dev/null +++ b/packages/worker/src/api/routes/system/flags.js @@ -0,0 +1,8 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/system/flags") + +const router = Router() + +router.get("/api/system/flags", controller.fetch) + +module.exports = router diff --git a/packages/worker/src/api/routes/system/tenants.js b/packages/worker/src/api/routes/system/tenants.js new file mode 100644 index 0000000000..223ba9f26e --- /dev/null +++ b/packages/worker/src/api/routes/system/tenants.js @@ -0,0 +1,11 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/system/tenants") +const adminOnly = require("../../../middleware/adminOnly") + +const router = Router() + +router + .get("/api/system/tenants/:tenantId/exists", controller.exists) + .get("/api/system/tenants", adminOnly, controller.fetch) + +module.exports = router From 7743384f7737105d1c127a6b75a8591eab687d1e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 2 Aug 2021 18:34:43 +0100 Subject: [PATCH 52/56] re-write, to use the ideas that Rory put in place, still WIP, un-tested but all implemented. --- packages/auth/db.js | 5 +- packages/auth/package.json | 1 + packages/auth/src/cache/user.js | 16 ++- packages/auth/src/db/constants.js | 17 +++ packages/auth/src/db/utils.js | 61 +--------- packages/auth/src/index.js | 7 +- packages/auth/src/middleware/authenticated.js | 39 ++----- packages/auth/src/middleware/index.js | 2 + packages/auth/src/middleware/matchers.js | 30 +++++ .../auth/src/middleware/passport/local.js | 9 +- .../middleware/passport/third-party-common.js | 11 +- packages/auth/src/middleware/tenancy.js | 23 ++++ packages/auth/src/tenancy/context.js | 87 +++++++++++++++ packages/auth/src/tenancy/index.js | 4 + packages/auth/src/tenancy/tenancy.js | 105 ++++++++++++++++++ packages/auth/src/utils.js | 28 +---- packages/auth/tenancy.js | 1 + packages/auth/yarn.lock | 35 +++++- .../server/src/api/controllers/apikeys.js | 17 +-- packages/server/src/api/index.js | 3 +- .../src/tests/utilities/TestConfiguration.js | 2 +- packages/server/src/utilities/global.js | 7 +- .../worker/src/api/controllers/global/auth.js | 34 +++--- .../src/api/controllers/global/configs.js | 23 ++-- .../src/api/controllers/global/email.js | 10 +- .../src/api/controllers/global/templates.js | 17 +-- .../src/api/controllers/global/users.js | 39 +++---- .../src/api/controllers/global/workspaces.js | 10 +- packages/worker/src/api/index.js | 14 ++- .../worker/src/constants/templates/index.js | 19 +--- packages/worker/src/utilities/email.js | 21 ++-- packages/worker/src/utilities/templates.js | 22 ++-- 32 files changed, 461 insertions(+), 258 deletions(-) create mode 100644 packages/auth/src/db/constants.js create mode 100644 packages/auth/src/middleware/matchers.js create mode 100644 packages/auth/src/middleware/tenancy.js create mode 100644 packages/auth/src/tenancy/context.js create mode 100644 packages/auth/src/tenancy/index.js create mode 100644 packages/auth/src/tenancy/tenancy.js create mode 100644 packages/auth/tenancy.js diff --git a/packages/auth/db.js b/packages/auth/db.js index 4b03ec36cc..a7b38821a7 100644 --- a/packages/auth/db.js +++ b/packages/auth/db.js @@ -1 +1,4 @@ -module.exports = require("./src/db/utils") +module.exports = { + ...require("./src/db/utils"), + ...require("./src/db/constants"), +} diff --git a/packages/auth/package.json b/packages/auth/package.json index 986e4da18d..2bcf581c41 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -13,6 +13,7 @@ "@techpass/passport-openidconnect": "^0.3.0", "aws-sdk": "^2.901.0", "bcryptjs": "^2.4.3", + "cls-hooked": "^4.2.2", "ioredis": "^4.27.1", "jsonwebtoken": "^8.5.1", "koa-passport": "^4.1.4", diff --git a/packages/auth/src/cache/user.js b/packages/auth/src/cache/user.js index 616612a588..d8c67a5854 100644 --- a/packages/auth/src/cache/user.js +++ b/packages/auth/src/cache/user.js @@ -1,18 +1,22 @@ -const { getGlobalDB } = require("../db/utils") const redis = require("../redis/authRedis") -const { lookupTenantId } = require("../utils") +const { + updateTenantId, + lookupTenantId, + getGlobalDB, + isTenantIdSet, +} = require("../tenancy") const EXPIRY_SECONDS = 3600 -exports.getUser = async (userId, tenantId = null) => { - if (!tenantId) { - tenantId = await lookupTenantId(userId) +exports.getUser = async userId => { + if (!isTenantIdSet()) { + updateTenantId(await lookupTenantId(userId)) } const client = await redis.getUserClient() // try cache let user = await client.get(userId) if (!user) { - user = await getGlobalDB(tenantId).get(userId) + user = await getGlobalDB().get(userId) client.store(userId, user, EXPIRY_SECONDS) } return user diff --git a/packages/auth/src/db/constants.js b/packages/auth/src/db/constants.js new file mode 100644 index 0000000000..227d793f3e --- /dev/null +++ b/packages/auth/src/db/constants.js @@ -0,0 +1,17 @@ +exports.SEPARATOR = "_" + +exports.StaticDatabases = { + GLOBAL: { + name: "global-db", + docs: { + apiKeys: "apikeys", + }, + }, + // contains information about tenancy and so on + PLATFORM_INFO: { + name: "global-info", + docs: { + tenants: "tenants", + }, + }, +} \ No newline at end of file diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 9b6f8fcbed..e53cb01123 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -3,29 +3,16 @@ const Replication = require("./Replication") const { getDB } = require("./index") const { DEFAULT_TENANT_ID } = require("../constants") const env = require("../environment") +const { StaticDatabases, SEPARATOR } = require("./constants") +const { getTenantId } = require("../tenancy") const UNICODE_MAX = "\ufff0" -const SEPARATOR = "_" exports.ViewNames = { USER_BY_EMAIL: "by_email", } -exports.StaticDatabases = { - GLOBAL: { - name: "global-db", - docs: { - apiKeys: "apikeys", - }, - }, - // contains information about tenancy and so on - PLATFORM_INFO: { - name: "global-info", - docs: { - tenants: "tenants", - }, - }, -} +exports.StaticDatabases = StaticDatabases const PRE_APP = "app" const PRE_DEV = "dev" @@ -74,45 +61,6 @@ function getDocParams(docType, docId = null, otherProps = {}) { } } -/** - * Gets the name of the global DB to connect to in a multi-tenancy system. - */ -exports.getGlobalDB = tenantId => { - // fallback for system pre multi-tenancy - let dbName = exports.StaticDatabases.GLOBAL.name - if (tenantId && tenantId !== DEFAULT_TENANT_ID) { - dbName = `${tenantId}${SEPARATOR}${dbName}` - } - if (env.MULTI_TENANCY && tenantId == null) { - throw "Cannot create global DB without tenantId" - } - return getDB(dbName) -} - -/** - * Given a koa context this tries to extra what tenant is being accessed. - */ -exports.getTenantIdFromCtx = (ctx, opts = { includeQuery: false }) => { - if (!ctx) { - return null - } - const user = ctx.user || {} - const params = ctx.request.params || {} - let query = {} - if (opts && opts.includeQuery) { - query = ctx.request.query || {} - } - return user.tenantId || params.tenantId || query.tenantId -} - -/** - * Given a koa context this tries to find the correct tenant Global DB. - */ -exports.getGlobalDBFromCtx = (ctx, opts) => { - const tenantId = exports.getTenantIdFromCtx(ctx, opts) - return exports.getGlobalDB(tenantId) -} - /** * Generates a new workspace ID. * @returns {string} The new workspace ID which the workspace doc can be stored under. @@ -216,7 +164,8 @@ 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, { tenantId, dev, all } = {}) => { +exports.getAllApps = async (CouchDB, { dev, all } = {}) => { + let tenantId = getTenantId() if (!env.MULTI_TENANCY && !tenantId) { tenantId = DEFAULT_TENANT_ID } diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index f13cb5d868..5421dea214 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -1,7 +1,8 @@ const passport = require("koa-passport") const LocalStrategy = require("passport-local").Strategy const JwtStrategy = require("passport-jwt").Strategy -const { getGlobalDB, StaticDatabases } = require("./db/utils") +const { StaticDatabases } = require("./db/utils") +const { getGlobalDB } = require("./tenancy") const { jwt, local, @@ -9,6 +10,7 @@ const { google, oidc, auditLog, + tenancy, } = require("./middleware") const { setDB } = require("./db") const userCache = require("./cache/user") @@ -20,7 +22,7 @@ passport.use(new JwtStrategy(jwt.options, jwt.authenticate)) passport.serializeUser((user, done) => done(null, user)) passport.deserializeUser(async (user, done) => { - const db = getGlobalDB(user.tenantId) + const db = getGlobalDB() try { const user = await db.get(user._id) @@ -54,6 +56,7 @@ module.exports = { google, oidc, jwt: require("jsonwebtoken"), + buildTenancyMiddleware: tenancy, auditLog, }, cache: { diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index fa28ca7ba5..cf9a19e58b 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -2,27 +2,10 @@ const { Cookies, Headers } = require("../constants") const { getCookie, clearCookie } = require("../utils") const { getUser } = require("../cache/user") const { getSession, updateSessionTTL } = require("../security/sessions") +const { buildMatcherRegex, matches } = require("./matchers") +const { isTenantIdSet, updateTenantId } = require("../tenancy") const env = require("../environment") -const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g - -function buildNoAuthRegex(patterns) { - return patterns.map(pattern => { - const isObj = typeof pattern === "object" && pattern.route - const method = isObj ? pattern.method : "GET" - let route = isObj ? pattern.route : pattern - - const matches = route.match(PARAM_REGEX) - if (matches) { - for (let match of matches) { - const pattern = "/.*" + (match.endsWith("/") ? "/" : "") - route = route.replace(match, pattern) - } - } - return { regex: new RegExp(route), method } - }) -} - function finalise( ctx, { authenticated, user, internal, version, publicEndpoint } = {} @@ -34,19 +17,14 @@ function finalise( ctx.version = version } -module.exports = (noAuthPatterns = [], opts) => { - const noAuthOptions = noAuthPatterns ? buildNoAuthRegex(noAuthPatterns) : [] +module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => { + const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : [] return async (ctx, next) => { let publicEndpoint = false const version = ctx.request.headers[Headers.API_VER] // the path is not authenticated - const found = noAuthOptions.find(({ regex, method }) => { - return ( - regex.test(ctx.request.url) && - ctx.request.method.toLowerCase() === method.toLowerCase() - ) - }) - if (found != null) { + const found = matches(ctx, noAuthOptions) + if (found) { publicEndpoint = true } try { @@ -64,7 +42,10 @@ module.exports = (noAuthPatterns = [], opts) => { error = "No session found" } else { try { - user = await getUser(userId, session.tenantId) + if (session.tenantId && !isTenantIdSet()) { + updateTenantId(session.tenantId) + } + user = await getUser(userId) delete user.password authenticated = true } catch (err) { diff --git a/packages/auth/src/middleware/index.js b/packages/auth/src/middleware/index.js index 35c7d9c388..689859a139 100644 --- a/packages/auth/src/middleware/index.js +++ b/packages/auth/src/middleware/index.js @@ -4,6 +4,7 @@ const google = require("./passport/google") const oidc = require("./passport/oidc") const authenticated = require("./authenticated") const auditLog = require("./auditLog") +const tenancy = require("./tenancy") module.exports = { google, @@ -12,4 +13,5 @@ module.exports = { local, authenticated, auditLog, + tenancy, } diff --git a/packages/auth/src/middleware/matchers.js b/packages/auth/src/middleware/matchers.js new file mode 100644 index 0000000000..79b14bc99d --- /dev/null +++ b/packages/auth/src/middleware/matchers.js @@ -0,0 +1,30 @@ +const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g + +exports.buildMatcherRegex = patterns => { + return patterns.map(pattern => { + const isObj = typeof pattern === "object" && pattern.route + const method = isObj ? pattern.method : "GET" + let route = isObj ? pattern.route : pattern + + const matches = route.match(PARAM_REGEX) + if (matches) { + for (let match of matches) { + const pattern = "/.*" + (match.endsWith("/") ? "/" : "") + route = route.replace(match, pattern) + } + } + return { regex: new RegExp(route), method } + }) +} + +exports.matches = (ctx, options) => { + return options.find(({ regex, method }) => { + const urlMatch = regex.test(ctx.request.url) + const methodMatch = + method === "ALL" + ? true + : ctx.request.method.toLowerCase() === method.toLowerCase() + + return urlMatch && methodMatch + }) +} \ No newline at end of file diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js index 22bcf51fc2..0db40d64eb 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/auth/src/middleware/passport/local.js @@ -1,11 +1,12 @@ const jwt = require("jsonwebtoken") -const { UserStatus, DEFAULT_TENANT_ID } = require("../../constants") +const { UserStatus } = require("../../constants") const { compare } = require("../../hashing") const env = require("../../environment") const { getGlobalUserByEmail } = require("../../utils") const { authError } = require("./utils") const { newid } = require("../../hashing") const { createASession } = require("../../security/sessions") +const { getTenantId } = require("../../tenancy") const INVALID_ERR = "Invalid Credentials" @@ -24,11 +25,8 @@ exports.options = { exports.authenticate = async function (ctx, email, password, done) { if (!email) return authError(done, "Email Required") if (!password) return authError(done, "Password Required") - const params = ctx.params || {} - // use the request to find the tenantId - let tenantId = params.tenantId || DEFAULT_TENANT_ID - const dbUser = await getGlobalUserByEmail(email, tenantId) + const dbUser = await getGlobalUserByEmail(email) if (dbUser == null) { return authError(done, "User not found") } @@ -41,6 +39,7 @@ exports.authenticate = async function (ctx, email, password, done) { // authenticate if (await compare(password, dbUser.password)) { const sessionId = newid() + const tenantId = getTenantId() await createASession(dbUser._id, { sessionId, tenantId }) dbUser.token = jwt.sign( diff --git a/packages/auth/src/middleware/passport/third-party-common.js b/packages/auth/src/middleware/passport/third-party-common.js index 094161ec7f..7490cc4031 100644 --- a/packages/auth/src/middleware/passport/third-party-common.js +++ b/packages/auth/src/middleware/passport/third-party-common.js @@ -1,10 +1,11 @@ const env = require("../../environment") const jwt = require("jsonwebtoken") -const { generateGlobalUserID, getGlobalDB } = require("../../db/utils") +const { generateGlobalUserID } = require("../../db/utils") const { authError } = require("./utils") const { newid } = require("../../hashing") const { createASession } = require("../../security/sessions") -const { getGlobalUserByEmail, lookupTenantId } = require("../../utils") +const { getGlobalUserByEmail } = require("../../utils") +const { getGlobalDB, getTenantId } = require("../../tenancy") /** * Common authentication logic for third parties. e.g. OAuth, OIDC. @@ -26,8 +27,7 @@ exports.authenticateThirdParty = async function ( // use the third party id const userId = generateGlobalUserID(thirdPartyUser.userId) - const tenantId = await lookupTenantId(userId) - const db = getGlobalDB(tenantId) + const db = getGlobalDB() let dbUser @@ -47,7 +47,7 @@ exports.authenticateThirdParty = async function ( // fallback to loading by email if (!dbUser) { - dbUser = await getGlobalUserByEmail(thirdPartyUser.email, tenantId) + dbUser = await getGlobalUserByEmail(thirdPartyUser.email) } // exit early if there is still no user and auto creation is disabled @@ -75,6 +75,7 @@ exports.authenticateThirdParty = async function ( // authenticate const sessionId = newid() + const tenantId = getTenantId() await createASession(dbUser._id, { sessionId, tenantId }) dbUser.token = jwt.sign( diff --git a/packages/auth/src/middleware/tenancy.js b/packages/auth/src/middleware/tenancy.js new file mode 100644 index 0000000000..3a1df833c1 --- /dev/null +++ b/packages/auth/src/middleware/tenancy.js @@ -0,0 +1,23 @@ +const { + createTenancyContext, + setTenantId, +} = require("../tenancy") +const { buildMatcherRegex, matches } = require("./matchers") + +module.exports = (allowQueryStringPatterns, noTenancyPatterns) => { + const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns) + const noTenancyOptions = buildMatcherRegex(noTenancyPatterns) + + return (ctx, next) => { + // always run in context + return createTenancyContext().runAndReturn(() => { + if (matches(ctx, noTenancyOptions)) { + return next() + } + + const allowQs = !!matches(ctx, allowQsOptions) + setTenantId(ctx, { allowQs }) + return next() + }) + } +} diff --git a/packages/auth/src/tenancy/context.js b/packages/auth/src/tenancy/context.js new file mode 100644 index 0000000000..1331dd0a35 --- /dev/null +++ b/packages/auth/src/tenancy/context.js @@ -0,0 +1,87 @@ +const cls = require("cls-hooked") +const env = require("../environment") +const { Headers } = require("../../constants") + +exports.DEFAULT_TENANT_ID = "default" + +exports.isDefaultTenant = () => { + return exports.getTenantId() === exports.DEFAULT_TENANT_ID +} + +exports.isMultiTenant = () => { + return env.MULTI_TENANCY +} + +// continuation local storage +const CONTEXT_NAME = "tenancy" +const TENANT_ID = "tenantId" + +exports.createTenancyContext = () => { + return cls.createNamespace(CONTEXT_NAME) +} + +const getTenancyContext = () => { + return cls.getNamespace(CONTEXT_NAME) +} + +// used for automations, API endpoints should always be in context already +exports.doInTenant = (tenantId, task) => { + const context = getTenancyContext() + return getTenancyContext().runAndReturn(() => { + // set the tenant id + context.set(TENANT_ID, tenantId) + + // invoke the task + const result = task() + + // clear down the tenant id manually for extra safety + // this should also happen automatically when the call exits + context.set(TENANT_ID, null) + + return result + }) +} + +exports.updateTenantId = tenantId => { + getTenancyContext().set(TENANT_ID, tenantId) +} + +exports.setTenantId = (ctx, opts = { allowQs: false }) => { + let tenantId + // exit early if not multi-tenant + if (!exports.isMultiTenant()) { + getTenancyContext().set(TENANT_ID, this.DEFAULT_TENANT_ID) + return + } + + const params = ctx.request.params || {} + const header = ctx.request.headers[Headers.TENANT_ID] + const user = ctx.request.user || {} + tenantId = user.tenantId || params.tenantId || header + if (opts.allowQs && !tenantId) { + const query = ctx.request.query || {} + tenantId = query.tenantId + } + + if (!tenantId) { + ctx.throw(403, "Tenant id not set") + } + + getTenancyContext().set(TENANT_ID, tenantId) +} + +exports.isTenantIdSet = () => { + const tenantId = getTenancyContext().get(TENANT_ID) + return !!tenantId +} + +exports.getTenantId = () => { + if (!exports.isMultiTenant()) { + return exports.DEFAULT_TENANT_ID + } + const tenantId = getTenancyContext().get(TENANT_ID) + if (!tenantId) { + throw Error("Tenant id not found") + } + return tenantId +} \ No newline at end of file diff --git a/packages/auth/src/tenancy/index.js b/packages/auth/src/tenancy/index.js new file mode 100644 index 0000000000..2fe257d885 --- /dev/null +++ b/packages/auth/src/tenancy/index.js @@ -0,0 +1,4 @@ +module.exports = { + ...require("./context"), + ...require("./tenancy"), +} diff --git a/packages/auth/src/tenancy/tenancy.js b/packages/auth/src/tenancy/tenancy.js new file mode 100644 index 0000000000..3c1c145938 --- /dev/null +++ b/packages/auth/src/tenancy/tenancy.js @@ -0,0 +1,105 @@ +const { getDB } = require("../../db") +const { SEPARATOR, StaticDatabases } = require("../db/constants") +const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("./context") +const env = require("../environment") + +const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants +const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name + +exports.addTenantToUrl = url => { + const tenantId = getTenantId() + + if (isMultiTenant()) { + const char = url.indexOf("?") === -1 ? "?" : "&" + url += `${char}tenantId=${tenantId}` + } + + return url +} + +exports.doesTenantExist = async tenantId => { + const db = getDB(PLATFORM_INFO_DB) + let tenants + try { + tenants = await db.get(TENANT_DOC) + } catch (err) { + // if theres an error the doc doesn't exist, no tenants exist + return false + } + return ( + tenants && + Array.isArray(tenants.tenantIds) && + tenants.tenantIds.indexOf(tenantId) !== -1 + ) +} + +exports.tryAddTenant = async (tenantId, userId, email) => { + const db = getDB(PLATFORM_INFO_DB) + const getDoc = async id => { + if (!id) { + return null + } + try { + return await db.get(id) + } catch (err) { + return { _id: id } + } + } + 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) + promises.push(db.put(tenants)) + } + await Promise.all(promises) +} + +exports.getGlobalDB = (tenantId = null) => { + // tenant ID can be set externally, for example user API where + // new tenants are being created, this may be the case + if (!tenantId) { + const tenantId = getTenantId() + } + + let dbName + + if (tenantId === DEFAULT_TENANT_ID) { + dbName = StaticDatabases.GLOBAL.name + } else { + dbName = `${tenantId}${SEPARATOR}${StaticDatabases.GLOBAL.name}` + } + + return getDB(dbName) +} + +exports.lookupTenantId = async userId => { + const db = getDB(StaticDatabases.PLATFORM_INFO.name) + let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null + try { + const doc = await db.get(userId) + if (doc && doc.tenantId) { + tenantId = doc.tenantId + } + } catch (err) { + // just return the default + } + return tenantId +} \ No newline at end of file diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index dd03f132a1..eee9de72fe 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -2,15 +2,12 @@ const { DocumentTypes, SEPARATOR, ViewNames, - StaticDatabases, } = require("./db/utils") 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 { DEFAULT_TENANT_ID, Headers } = require("./constants") -const env = require("./environment") +const { Headers } = require("./constants") +const { getGlobalDB } = require("./tenancy") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -103,32 +100,17 @@ exports.isClient = ctx => { return ctx.headers[Headers.TYPE] === "client" } -exports.lookupTenantId = async userId => { - const db = getDB(StaticDatabases.PLATFORM_INFO.name) - let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null - try { - const doc = await db.get(userId) - if (doc && doc.tenantId) { - tenantId = doc.tenantId - } - } catch (err) { - // just return the default - } - return 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, tenantId) => { +exports.getGlobalUserByEmail = async email => { if (email == null) { throw "Must supply an email address to view" } - const db = getGlobalDB(tenantId) + const db = getGlobalDB() try { let users = ( await db.query(`database/${ViewNames.USER_BY_EMAIL}`, { @@ -141,7 +123,7 @@ exports.getGlobalUserByEmail = async (email, tenantId) => { } catch (err) { if (err != null && err.name === "not_found") { await createUserEmailView(db) - return exports.getGlobalUserByEmail(email, tenantId) + return exports.getGlobalUserByEmail(email) } else { throw err } diff --git a/packages/auth/tenancy.js b/packages/auth/tenancy.js new file mode 100644 index 0000000000..9ca808b74e --- /dev/null +++ b/packages/auth/tenancy.js @@ -0,0 +1 @@ +module.exports = require("./src/tenancy") diff --git a/packages/auth/yarn.lock b/packages/auth/yarn.lock index 8957ecb0fc..b6be8ad1e8 100644 --- a/packages/auth/yarn.lock +++ b/packages/auth/yarn.lock @@ -798,6 +798,13 @@ ast-types@0.9.6: resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk= +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + async@~2.1.4: version "2.1.5" resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc" @@ -1144,6 +1151,15 @@ clone-buffer@1.0.0: resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + cluster-key-slot@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" @@ -1444,6 +1460,13 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.775.tgz#046517d1f2cea753e06fff549995b9dc45e20082" integrity sha512-EGuiJW4yBPOTj2NtWGZcX93ZE8IGj33HJAx4d3ouE2zOfW2trbWU+t1e0yzLr1qQIw81++txbM3BH52QwSRE6Q== +emitter-listener@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + emittery@^0.7.1: version "0.7.2" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" @@ -4035,7 +4058,7 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -4096,6 +4119,11 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== +shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -4250,6 +4278,11 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= + stack-utils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" diff --git a/packages/server/src/api/controllers/apikeys.js b/packages/server/src/api/controllers/apikeys.js index 7144788945..9b1ddee4c4 100644 --- a/packages/server/src/api/controllers/apikeys.js +++ b/packages/server/src/api/controllers/apikeys.js @@ -1,9 +1,10 @@ -const { StaticDatabases, getGlobalDBFromCtx } = require("@budibase/auth/db") +const { StaticDatabases } = require("@budibase/auth/db") +const { getGlobalDB } = require("@budibase/auth/tenancy") const KEYS_DOC = StaticDatabases.GLOBAL.docs.apiKeys -async function getBuilderMainDoc(ctx) { - const db = getGlobalDBFromCtx(ctx) +async function getBuilderMainDoc() { + const db = getGlobalDB() try { return await db.get(KEYS_DOC) } catch (err) { @@ -14,16 +15,16 @@ async function getBuilderMainDoc(ctx) { } } -async function setBuilderMainDoc(ctx, doc) { +async function setBuilderMainDoc(doc) { // make sure to override the ID doc._id = KEYS_DOC - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() return db.put(doc) } exports.fetch = async function (ctx) { try { - const mainDoc = await getBuilderMainDoc(ctx) + const mainDoc = await getBuilderMainDoc() ctx.body = mainDoc.apiKeys ? mainDoc.apiKeys : {} } catch (err) { /* istanbul ignore next */ @@ -36,12 +37,12 @@ exports.update = async function (ctx) { const value = ctx.request.body.value try { - const mainDoc = await getBuilderMainDoc(ctx) + const mainDoc = await getBuilderMainDoc() if (mainDoc.apiKeys == null) { mainDoc.apiKeys = {} } mainDoc.apiKeys[key] = value - const resp = await setBuilderMainDoc(ctx, mainDoc) + const resp = await setBuilderMainDoc(mainDoc) ctx.body = { _id: resp.id, _rev: resp.rev, diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 6c4188a5dc..8c940a5a50 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -1,5 +1,5 @@ const Router = require("@koa/router") -const { buildAuthMiddleware, auditLog } = require("@budibase/auth").auth +const { buildAuthMiddleware, auditLog, buildTenancyMiddleware } = require("@budibase/auth").auth const currentApp = require("../middleware/currentapp") const compress = require("koa-compress") const zlib = require("zlib") @@ -31,6 +31,7 @@ router }) .use("/health", ctx => (ctx.status = 200)) .use("/version", ctx => (ctx.body = pkg.version)) + .use(buildTenancyMiddleware()) .use( buildAuthMiddleware(null, { publicAllowed: true, diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index ad49b0ef2e..0eb3851d98 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -18,7 +18,7 @@ const { cleanup } = require("../../utilities/fileSystem") const { Cookies, Headers } = require("@budibase/auth").constants const { jwt } = require("@budibase/auth").auth const auth = require("@budibase/auth") -const { getGlobalDB } = require("@budibase/auth/db") +const { getGlobalDB } = require("@budibase/auth/tenancy") const { createASession } = require("@budibase/auth/sessions") const { user: userCache } = require("@budibase/auth/cache") const CouchDB = require("../../db") diff --git a/packages/server/src/utilities/global.js b/packages/server/src/utilities/global.js index 2dbb956d33..d3e9701b62 100644 --- a/packages/server/src/utilities/global.js +++ b/packages/server/src/utilities/global.js @@ -3,9 +3,10 @@ const { getGlobalIDFromUserMetadataID, } = require("../db/utils") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") -const { getDeployedAppID, getGlobalDBFromCtx } = require("@budibase/auth/db") +const { getDeployedAppID } = require("@budibase/auth/db") const { getGlobalUserParams } = require("@budibase/auth/db") const { user: userCache } = require("@budibase/auth/cache") +const { getGlobalDB } = require("@budibase/auth/tenancy") exports.updateAppRole = (appId, user) => { if (!user.roles) { @@ -37,13 +38,13 @@ exports.getCachedSelf = async (ctx, appId) => { } exports.getGlobalUser = async (ctx, appId, userId) => { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() let user = await db.get(getGlobalIDFromUserMetadataID(userId)) return processUser(appId, user) } exports.getGlobalUsers = async (ctx, appId = null, users = null) => { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() let globalUsers if (users) { const globalIds = users.map(user => getGlobalIDFromUserMetadataID(user._id)) diff --git a/packages/worker/src/api/controllers/global/auth.js b/packages/worker/src/api/controllers/global/auth.js index 1275084687..4f02838282 100644 --- a/packages/worker/src/api/controllers/global/auth.js +++ b/packages/worker/src/api/controllers/global/auth.js @@ -8,13 +8,13 @@ const { setCookie, getCookie, clearCookie, getGlobalUserByEmail, hash } = const { Cookies } = authPkg.constants const { passport } = authPkg.auth const { checkResetPasswordCode } = require("../../../utilities/redis") -const { getGlobalDB } = authPkg.db +const { getGlobalDB, getTenantId, isMultiTenant } = require("@budibase/auth/tenancy") const env = require("../../../environment") -function googleCallbackUrl(tenantId = null) { +function googleCallbackUrl() { let callbackUrl = `/api/global/auth` - if (tenantId) { - callbackUrl += `/${tenantId}` + if (isMultiTenant()) { + callbackUrl += `/${getTenantId()}` } callbackUrl += `/google/callback` return callbackUrl @@ -57,8 +57,7 @@ exports.authenticate = async (ctx, next) => { */ exports.reset = async ctx => { const { email } = ctx.request.body - const tenantId = ctx.params.tenantId - const configured = await isEmailConfigured(tenantId) + const configured = await isEmailConfigured() if (!configured) { ctx.throw( 400, @@ -66,10 +65,10 @@ exports.reset = async ctx => { ) } try { - const user = await getGlobalUserByEmail(email, tenantId) + const user = await getGlobalUserByEmail(email) // only if user exists, don't error though if they don't if (user) { - await sendEmail(tenantId, email, EmailTemplatePurpose.PASSWORD_RECOVERY, { + await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, { user, subject: "{{ company }} platform password reset", }) @@ -90,7 +89,7 @@ exports.resetUpdate = async ctx => { const { resetCode, password } = ctx.request.body try { const userId = await checkResetPasswordCode(resetCode) - const db = getGlobalDB(ctx.params.tenantId) + const db = getGlobalDB() const user = await db.get(userId) user.password = await hash(password) await db.put(user) @@ -112,9 +111,8 @@ 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 ? ctx.params.tenantId : null - const db = getGlobalDB(tenantId) - let callbackUrl = googleCallbackUrl(tenantId) + const db = getGlobalDB() + let callbackUrl = googleCallbackUrl() const config = await authPkg.db.getScopedConfig(db, { type: Configs.GOOGLE, @@ -128,9 +126,8 @@ exports.googlePreAuth = async (ctx, next) => { } exports.googleAuth = async (ctx, next) => { - const tenantId = ctx.params ? ctx.params.tenantId : null - const db = getGlobalDB(tenantId) - const callbackUrl = googleCallbackUrl(tenantId) + const db = getGlobalDB() + const callbackUrl = googleCallbackUrl() const config = await authPkg.db.getScopedConfig(db, { type: Configs.GOOGLE, @@ -150,8 +147,7 @@ exports.googleAuth = async (ctx, next) => { } async function oidcStrategyFactory(ctx, configId) { - const tenantId = ctx.params ? ctx.params.tenantId : null - const db = getGlobalDB(ctx.params.tenantId) + const db = getGlobalDB() const config = await authPkg.db.getScopedConfig(db, { type: Configs.OIDC, group: ctx.query.group, @@ -161,8 +157,8 @@ async function oidcStrategyFactory(ctx, configId) { const protocol = env.NODE_ENV === "production" ? "https" : "http" let callbackUrl = `${protocol}://${ctx.host}/api/global/auth` - if (tenantId) { - callbackUrl += `/${tenantId}` + if (isMultiTenant()) { + callbackUrl += `/${getTenantId()}` } callbackUrl += `/oidc/callback` diff --git a/packages/worker/src/api/controllers/global/configs.js b/packages/worker/src/api/controllers/global/configs.js index 000ce85381..8b4807b684 100644 --- a/packages/worker/src/api/controllers/global/configs.js +++ b/packages/worker/src/api/controllers/global/configs.js @@ -3,17 +3,16 @@ const { getConfigParams, getGlobalUserParams, getScopedFullConfig, - getGlobalDBFromCtx, - getTenantIdFromCtx, getAllApps, } = require("@budibase/auth/db") const { Configs } = require("../../../constants") const email = require("../../../utilities/email") const { upload, ObjectStoreBuckets } = require("@budibase/auth").objectStore const CouchDB = require("../../../db") +const { getGlobalDB } = require("@budibase/auth/tenancy") exports.save = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() const { type, workspace, user, config } = ctx.request.body // Config does not exist yet @@ -49,7 +48,7 @@ exports.save = async function (ctx) { } exports.fetch = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() const response = await db.allDocs( getConfigParams( { type: ctx.params.type }, @@ -66,7 +65,7 @@ exports.fetch = async function (ctx) { * The hierarchy is type -> workspace -> user. */ exports.find = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() const { userId, workspaceId } = ctx.query if (workspaceId && userId) { @@ -99,7 +98,7 @@ exports.find = async function (ctx) { } exports.publicOidc = async function (ctx) { - const db = getGlobalDBFromCtx(ctx, { includeQuery: true }) + const db = getGlobalDB() try { // Find the config with the most granular scope based on context const oidcConfig = await getScopedFullConfig(db, { @@ -121,7 +120,7 @@ exports.publicOidc = async function (ctx) { } exports.publicSettings = async function (ctx) { - const db = getGlobalDBFromCtx(ctx, { includeQuery: true }) + const db = getGlobalDB() try { // Find the config with the most granular scope based on context @@ -186,7 +185,7 @@ exports.upload = async function (ctx) { // add to configuration structure // TODO: right now this only does a global level - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() let cfgStructure = await getScopedFullConfig(db, { type }) if (!cfgStructure) { cfgStructure = { @@ -206,7 +205,7 @@ exports.upload = async function (ctx) { } exports.destroy = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() const { id, rev } = ctx.params try { @@ -218,15 +217,13 @@ exports.destroy = async function (ctx) { } exports.configChecklist = async function (ctx) { - // include the query string only for a select few endpoints - const tenantId = getTenantIdFromCtx(ctx, { includeQuery: true }) - const db = getGlobalDBFromCtx(ctx, { includeQuery: true }) + const db = getGlobalDB() try { // TODO: Watch get started video // Apps exist - const apps = await getAllApps(CouchDB, { tenantId }) + const apps = await getAllApps(CouchDB) // They have set up SMTP const smtpConfig = await getScopedFullConfig(db, { diff --git a/packages/worker/src/api/controllers/global/email.js b/packages/worker/src/api/controllers/global/email.js index 11841d1b56..50e7acdb1a 100644 --- a/packages/worker/src/api/controllers/global/email.js +++ b/packages/worker/src/api/controllers/global/email.js @@ -1,9 +1,8 @@ const { sendEmail } = require("../../../utilities/email") -const { getGlobalDBFromCtx } = require("@budibase/auth/db") +const { getGlobalDB } = require("@budibase/auth/tenancy") exports.sendEmail = async ctx => { let { - tenantId, workspaceId, email, userId, @@ -14,13 +13,10 @@ exports.sendEmail = async ctx => { } = ctx.request.body let user if (userId) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() user = await db.get(userId) } - if (!tenantId && ctx.user.tenantId) { - tenantId = ctx.user.tenantId - } - const response = await sendEmail(tenantId, email, purpose, { + const response = await sendEmail(email, purpose, { workspaceId, user, contents, diff --git a/packages/worker/src/api/controllers/global/templates.js b/packages/worker/src/api/controllers/global/templates.js index cf9e988b6c..0dc2b8abab 100644 --- a/packages/worker/src/api/controllers/global/templates.js +++ b/packages/worker/src/api/controllers/global/templates.js @@ -1,13 +1,14 @@ -const { generateTemplateID, getGlobalDBFromCtx } = require("@budibase/auth/db") +const { generateTemplateID } = require("@budibase/auth/db") const { TemplateMetadata, TemplateBindings, GLOBAL_OWNER, } = require("../../../constants") -const { getTemplatesCtx } = require("../../../constants/templates") +const { getTemplates } = require("../../../constants/templates") +const { getGlobalDB } = require("@budibase/auth/tenancy") exports.save = async ctx => { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() let template = ctx.request.body if (!template.ownerId) { template.ownerId = GLOBAL_OWNER @@ -45,29 +46,29 @@ exports.definitions = async ctx => { } exports.fetch = async ctx => { - ctx.body = await getTemplatesCtx(ctx) + ctx.body = await getTemplates() } exports.fetchByType = async ctx => { - ctx.body = await getTemplatesCtx(ctx, { + ctx.body = await getTemplates({ type: ctx.params.type, }) } exports.fetchByOwner = async ctx => { - ctx.body = await getTemplatesCtx(ctx, { + ctx.body = await getTemplates({ ownerId: ctx.params.ownerId, }) } exports.find = async ctx => { - ctx.body = await getTemplatesCtx(ctx, { + ctx.body = await getTemplates({ id: ctx.params.id, }) } exports.destroy = async ctx => { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() 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/global/users.js b/packages/worker/src/api/controllers/global/users.js index f6bf76c9c1..3153328ace 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -1,8 +1,7 @@ const { generateGlobalUserID, getGlobalUserParams, - getGlobalDB, - getGlobalDBFromCtx, + StaticDatabases, } = require("@budibase/auth/db") const { hash, getGlobalUserByEmail } = require("@budibase/auth").utils @@ -14,6 +13,7 @@ const { user: userCache } = require("@budibase/auth/cache") const { invalidateSessions } = require("@budibase/auth/sessions") const CouchDB = require("../../../db") const env = require("../../../environment") +const { getGlobalDB, getTenantId } = require("@budibase/auth/tenancy") const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants @@ -73,8 +73,8 @@ async function doesTenantExist(tenantId) { ) } -async function allUsers(ctx) { - const db = getGlobalDBFromCtx(ctx) +async function allUsers() { + const db = getGlobalDB() const response = await db.allDocs( getGlobalUserParams(null, { include_docs: true, @@ -87,12 +87,13 @@ async function saveUser(user, tenantId) { if (!tenantId) { throw "No tenancy specified." } + // specify the tenancy incase we're making a new admin user (public) const db = getGlobalDB(tenantId) let { email, password, _id } = user // make sure another user isn't using the same email let dbUser if (email) { - dbUser = await getGlobalUserByEmail(email, tenantId) + dbUser = await getGlobalUserByEmail(email) if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) { throw "Email address already in use." } @@ -148,10 +149,8 @@ async function saveUser(user, tenantId) { } 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) + ctx.body = await saveUser(ctx.request.body, getTenantId()) } catch (err) { ctx.throw(err.status || 400, err) } @@ -163,7 +162,7 @@ exports.adminUser = async ctx => { ctx.throw(403, "Organisation already exists.") } - const db = getGlobalDB(tenantId) + const db = getGlobalDB() const response = await db.allDocs( getGlobalUserParams(null, { include_docs: true, @@ -197,7 +196,7 @@ exports.adminUser = async ctx => { } exports.destroy = async ctx => { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() const dbUser = await db.get(ctx.params.id) await db.remove(dbUser._id, dbUser._rev) await userCache.invalidateUser(dbUser._id) @@ -209,7 +208,7 @@ exports.destroy = async ctx => { exports.removeAppRole = async ctx => { const { appId } = ctx.params - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() const users = await allUsers(ctx) const bulk = [] const cacheInvalidations = [] @@ -239,7 +238,7 @@ exports.getSelf = async ctx => { } exports.updateSelf = async ctx => { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() const user = await db.get(ctx.user._id) if (ctx.request.body.password) { ctx.request.body.password = await hash(ctx.request.body.password) @@ -272,7 +271,7 @@ exports.fetch = async ctx => { // called internally by app server user find exports.find = async ctx => { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() let user try { user = await db.get(ctx.params.id) @@ -310,16 +309,14 @@ exports.tenantLookup = async ctx => { exports.invite = async ctx => { let { email, userInfo } = ctx.request.body - const tenantId = ctx.user.tenantId - const existing = await getGlobalUserByEmail(email, tenantId) + const existing = await getGlobalUserByEmail(email) if (existing) { ctx.throw(400, "Email address already in use.") } if (!userInfo) { userInfo = {} } - userInfo.tenantId = tenantId - await sendEmail(tenantId, email, EmailTemplatePurpose.INVITATION, { + await sendEmail(email, EmailTemplatePurpose.INVITATION, { subject: "{{ company }} platform invitation", info: userInfo, }) @@ -333,17 +330,13 @@ exports.inviteAccept = async ctx => { try { // 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 = { + ctx.body = await saveUser({ firstName, lastName, password, email, ...info, - } - ctx.user = { - tenantId: info.tenantId, - } + }, info.tenantId) // this will flesh out the body response await exports.save(ctx) } catch (err) { diff --git a/packages/worker/src/api/controllers/global/workspaces.js b/packages/worker/src/api/controllers/global/workspaces.js index e2910a2364..fb1a859826 100644 --- a/packages/worker/src/api/controllers/global/workspaces.js +++ b/packages/worker/src/api/controllers/global/workspaces.js @@ -1,11 +1,11 @@ const { getWorkspaceParams, generateWorkspaceID, - getGlobalDBFromCtx, } = require("@budibase/auth/db") +const { getGlobalDB } = require("@budibase/auth/tenancy") exports.save = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() const workspaceDoc = ctx.request.body // workspace does not exist yet @@ -25,7 +25,7 @@ exports.save = async function (ctx) { } exports.fetch = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() const response = await db.allDocs( getWorkspaceParams(undefined, { include_docs: true, @@ -35,7 +35,7 @@ exports.fetch = async function (ctx) { } exports.find = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() try { ctx.body = await db.get(ctx.params.id) } catch (err) { @@ -44,7 +44,7 @@ exports.find = async function (ctx) { } exports.destroy = async function (ctx) { - const db = getGlobalDBFromCtx(ctx) + const db = getGlobalDB() const { id, rev } = ctx.params try { diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js index 2e65dc17e7..d39546ff3a 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -2,7 +2,18 @@ const Router = require("@koa/router") const compress = require("koa-compress") const zlib = require("zlib") const { routes } = require("./routes") -const { buildAuthMiddleware, auditLog } = require("@budibase/auth").auth +const { buildAuthMiddleware, auditLog, buildTenancyMiddleware } = require("@budibase/auth").auth + +const NO_TENANCY_ENDPOINTS = [ + { + route: "/api/system", + method: "ALL", + }, + { + route: "/api/global/users/self", + method: "GET", + } +] const PUBLIC_ENDPOINTS = [ { @@ -53,6 +64,7 @@ router }) ) .use("/health", ctx => (ctx.status = 200)) + .use(buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_ENDPOINTS)) .use(buildAuthMiddleware(PUBLIC_ENDPOINTS)) // for now no public access is allowed to worker (bar health check) .use((ctx, next) => { diff --git a/packages/worker/src/constants/templates/index.js b/packages/worker/src/constants/templates/index.js index 805e4b79b7..44fe345af1 100644 --- a/packages/worker/src/constants/templates/index.js +++ b/packages/worker/src/constants/templates/index.js @@ -8,9 +8,8 @@ const { const { join } = require("path") const { getTemplateParams, - getTenantIdFromCtx, - getGlobalDB, } = require("@budibase/auth/db") +const { getGlobalDB } = require("@budibase/auth/tenancy") exports.EmailTemplates = { [EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile( @@ -52,13 +51,8 @@ exports.addBaseTemplates = (templates, type = null) => { return templates } -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) +exports.getTemplates = async ({ ownerId, type, id } = {}) => { + const db = getGlobalDB() const response = await db.allDocs( getTemplateParams(ownerId || GLOBAL_OWNER, id, { include_docs: true, @@ -75,10 +69,7 @@ exports.getTemplates = async (tenantId, { ownerId, type, id } = {}) => { return exports.addBaseTemplates(templates, type) } -exports.getTemplateByPurpose = async ({ tenantId, ctx }, type, purpose) => { - if (!tenantId && ctx) { - tenantId = getTenantIdFromCtx(ctx) - } - const templates = await exports.getTemplates(tenantId, { type }) +exports.getTemplateByPurpose = async (type, purpose) => { + const templates = await exports.getTemplates({ 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 eec2743b69..22de6b4d6d 100644 --- a/packages/worker/src/utilities/email.js +++ b/packages/worker/src/utilities/email.js @@ -1,10 +1,11 @@ const nodemailer = require("nodemailer") -const { getGlobalDB, getScopedConfig } = require("@budibase/auth/db") +const { getScopedConfig } = require("@budibase/auth/db") const { EmailTemplatePurpose, TemplateTypes, Configs } = require("../constants") const { getTemplateByPurpose } = require("../constants/templates") const { getSettingsTemplateContext } = require("./templates") const { processString } = require("@budibase/string-templates") const { getResetPasswordCode, getInviteCode } = require("../utilities/redis") +const { getGlobalDB } = require("@budibase/auth/tenancy") const TEST_MODE = false const TYPE = TemplateTypes.EMAIL @@ -60,7 +61,6 @@ 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). @@ -69,7 +69,6 @@ async function getLinkCode(purpose, email, user, info = null) { * @return {Promise} returns the built email HTML if all provided parameters were valid. */ async function buildEmail( - tenantId, purpose, email, context, @@ -80,8 +79,8 @@ async function buildEmail( throw `Unable to build an email of type ${purpose}` } let [base, body] = await Promise.all([ - getTemplateByPurpose({ tenantId }, TYPE, EmailTemplatePurpose.BASE), - getTemplateByPurpose({ tenantId }, TYPE, purpose), + getTemplateByPurpose(TYPE, EmailTemplatePurpose.BASE), + getTemplateByPurpose(TYPE, purpose), ]) if (!base || !body) { throw "Unable to build email, missing base components" @@ -123,12 +122,12 @@ async function getSmtpConfiguration(db, workspaceId = null) { * Checks if a SMTP config exists based on passed in parameters. * @return {Promise} returns true if there is a configuration that can be used. */ -exports.isEmailConfigured = async (tenantId, workspaceId = null) => { +exports.isEmailConfigured = async (workspaceId = null) => { // when "testing" simply return true if (TEST_MODE) { return true } - const db = getGlobalDB(tenantId) + const db = getGlobalDB() const config = await getSmtpConfiguration(db, workspaceId) return config != null } @@ -136,7 +135,6 @@ exports.isEmailConfigured = async (tenantId, 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. @@ -149,12 +147,11 @@ exports.isEmailConfigured = async (tenantId, workspaceId = null) => { * nodemailer response. */ exports.sendEmail = async ( - tenantId, email, purpose, { workspaceId, user, from, contents, subject, info } = {} ) => { - const db = getGlobalDB(tenantId) + const db = getGlobalDB() let config = (await getSmtpConfiguration(db, workspaceId)) || {} if (Object.keys(config).length === 0 && !TEST_MODE) { throw "Unable to find SMTP configuration." @@ -162,11 +159,11 @@ 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(tenantId, purpose, code) + const context = await getSettingsTemplateContext(purpose, code) const message = { from: from || config.from, to: email, - html: await buildEmail(tenantId, purpose, email, context, { + html: await buildEmail(purpose, email, context, { user, contents, }), diff --git a/packages/worker/src/utilities/templates.js b/packages/worker/src/utilities/templates.js index 51628064bb..31d92e9226 100644 --- a/packages/worker/src/utilities/templates.js +++ b/packages/worker/src/utilities/templates.js @@ -1,4 +1,4 @@ -const { getScopedConfig, getGlobalDB } = require("@budibase/auth/db") +const { getScopedConfig } = require("@budibase/auth/db") const { Configs, InternalTemplateBindings, @@ -7,20 +7,13 @@ const { } = require("../constants") const { checkSlashesInUrl } = require("./index") const env = require("../environment") +const { getGlobalDB, addTenantToUrl } = require("@budibase/auth/tenancy") 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 = getGlobalDB(tenantId) +exports.getSettingsTemplateContext = async (purpose, code = null) => { + const db = getGlobalDB() // TODO: use more granular settings in the future if required let settings = (await getScopedConfig(db, { type: Configs.SETTINGS })) || {} if (!settings || !settings.platformUrl) { @@ -35,7 +28,7 @@ exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => { [InternalTemplateBindings.DOCS_URL]: settings.docsUrl || "https://docs.budibase.com/", [InternalTemplateBindings.LOGIN_URL]: checkSlashesInUrl( - addTenantToUrl(`${URL}/login`, tenantId) + addTenantToUrl(`${URL}/login`) ), [InternalTemplateBindings.CURRENT_DATE]: new Date().toISOString(), [InternalTemplateBindings.CURRENT_YEAR]: new Date().getFullYear(), @@ -45,15 +38,14 @@ exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => { case EmailTemplatePurpose.PASSWORD_RECOVERY: context[InternalTemplateBindings.RESET_CODE] = code context[InternalTemplateBindings.RESET_URL] = checkSlashesInUrl( - addTenantToUrl(`${URL}/builder/auth/reset?code=${code}`, tenantId) + addTenantToUrl(`${URL}/builder/auth/reset?code=${code}`) ) break case EmailTemplatePurpose.INVITATION: context[InternalTemplateBindings.INVITE_CODE] = code context[InternalTemplateBindings.INVITE_URL] = checkSlashesInUrl( addTenantToUrl( - `${URL}/builder/invite?code=${code}&tenantId=${tenantId}`, - tenantId + `${URL}/builder/invite?code=${code}` ) ) break From f3ce9792306b2cb2006015beab460bb7f5450aea Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 2 Aug 2021 18:36:32 +0100 Subject: [PATCH 53/56] Linting. --- packages/auth/src/db/constants.js | 2 +- packages/auth/src/db/utils.js | 1 - packages/auth/src/middleware/matchers.js | 2 +- packages/auth/src/middleware/tenancy.js | 5 +---- packages/auth/src/tenancy/context.js | 2 +- packages/auth/src/tenancy/tenancy.js | 4 ++-- packages/auth/src/utils.js | 6 +----- packages/server/src/api/index.js | 3 ++- .../worker/src/api/controllers/global/auth.js | 6 +++++- .../worker/src/api/controllers/global/email.js | 11 ++--------- .../worker/src/api/controllers/global/users.js | 17 ++++++++++------- .../src/api/controllers/global/workspaces.js | 5 +---- packages/worker/src/api/index.js | 5 +++-- .../worker/src/constants/templates/index.js | 4 +--- packages/worker/src/utilities/email.js | 7 +------ packages/worker/src/utilities/templates.js | 4 +--- 16 files changed, 33 insertions(+), 51 deletions(-) diff --git a/packages/auth/src/db/constants.js b/packages/auth/src/db/constants.js index 227d793f3e..77643ce4c5 100644 --- a/packages/auth/src/db/constants.js +++ b/packages/auth/src/db/constants.js @@ -14,4 +14,4 @@ exports.StaticDatabases = { tenants: "tenants", }, }, -} \ No newline at end of file +} diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index e53cb01123..7d3a69ccd7 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -1,6 +1,5 @@ const { newid } = require("../hashing") const Replication = require("./Replication") -const { getDB } = require("./index") const { DEFAULT_TENANT_ID } = require("../constants") const env = require("../environment") const { StaticDatabases, SEPARATOR } = require("./constants") diff --git a/packages/auth/src/middleware/matchers.js b/packages/auth/src/middleware/matchers.js index 79b14bc99d..f46f0d781b 100644 --- a/packages/auth/src/middleware/matchers.js +++ b/packages/auth/src/middleware/matchers.js @@ -27,4 +27,4 @@ exports.matches = (ctx, options) => { return urlMatch && methodMatch }) -} \ No newline at end of file +} diff --git a/packages/auth/src/middleware/tenancy.js b/packages/auth/src/middleware/tenancy.js index 3a1df833c1..9242f11c26 100644 --- a/packages/auth/src/middleware/tenancy.js +++ b/packages/auth/src/middleware/tenancy.js @@ -1,7 +1,4 @@ -const { - createTenancyContext, - setTenantId, -} = require("../tenancy") +const { createTenancyContext, setTenantId } = require("../tenancy") const { buildMatcherRegex, matches } = require("./matchers") module.exports = (allowQueryStringPatterns, noTenancyPatterns) => { diff --git a/packages/auth/src/tenancy/context.js b/packages/auth/src/tenancy/context.js index 1331dd0a35..5210700bd0 100644 --- a/packages/auth/src/tenancy/context.js +++ b/packages/auth/src/tenancy/context.js @@ -84,4 +84,4 @@ exports.getTenantId = () => { throw Error("Tenant id not found") } return tenantId -} \ No newline at end of file +} diff --git a/packages/auth/src/tenancy/tenancy.js b/packages/auth/src/tenancy/tenancy.js index 3c1c145938..16dedd3ebf 100644 --- a/packages/auth/src/tenancy/tenancy.js +++ b/packages/auth/src/tenancy/tenancy.js @@ -76,7 +76,7 @@ exports.getGlobalDB = (tenantId = null) => { // tenant ID can be set externally, for example user API where // new tenants are being created, this may be the case if (!tenantId) { - const tenantId = getTenantId() + tenantId = getTenantId() } let dbName @@ -102,4 +102,4 @@ exports.lookupTenantId = async userId => { // just return the default } return tenantId -} \ No newline at end of file +} diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index eee9de72fe..5936948fd7 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -1,8 +1,4 @@ -const { - DocumentTypes, - SEPARATOR, - ViewNames, -} = require("./db/utils") +const { DocumentTypes, SEPARATOR, ViewNames } = require("./db/utils") const jwt = require("jsonwebtoken") const { options } = require("./middleware/passport/jwt") const { createUserEmailView } = require("./db/views") diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 8c940a5a50..13fb7efd1d 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -1,5 +1,6 @@ const Router = require("@koa/router") -const { buildAuthMiddleware, auditLog, buildTenancyMiddleware } = require("@budibase/auth").auth +const { buildAuthMiddleware, auditLog, buildTenancyMiddleware } = + require("@budibase/auth").auth const currentApp = require("../middleware/currentapp") const compress = require("koa-compress") const zlib = require("zlib") diff --git a/packages/worker/src/api/controllers/global/auth.js b/packages/worker/src/api/controllers/global/auth.js index 4f02838282..b08d51e642 100644 --- a/packages/worker/src/api/controllers/global/auth.js +++ b/packages/worker/src/api/controllers/global/auth.js @@ -8,7 +8,11 @@ const { setCookie, getCookie, clearCookie, getGlobalUserByEmail, hash } = const { Cookies } = authPkg.constants const { passport } = authPkg.auth const { checkResetPasswordCode } = require("../../../utilities/redis") -const { getGlobalDB, getTenantId, isMultiTenant } = require("@budibase/auth/tenancy") +const { + getGlobalDB, + getTenantId, + isMultiTenant, +} = require("@budibase/auth/tenancy") const env = require("../../../environment") function googleCallbackUrl() { diff --git a/packages/worker/src/api/controllers/global/email.js b/packages/worker/src/api/controllers/global/email.js index 50e7acdb1a..57b78a6d7a 100644 --- a/packages/worker/src/api/controllers/global/email.js +++ b/packages/worker/src/api/controllers/global/email.js @@ -2,15 +2,8 @@ const { sendEmail } = require("../../../utilities/email") const { getGlobalDB } = require("@budibase/auth/tenancy") exports.sendEmail = async ctx => { - let { - workspaceId, - email, - userId, - purpose, - contents, - from, - subject, - } = ctx.request.body + let { workspaceId, email, userId, purpose, contents, from, subject } = + ctx.request.body let user if (userId) { const db = getGlobalDB() diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index 3153328ace..e2284b09a8 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -330,13 +330,16 @@ exports.inviteAccept = async ctx => { try { // info is an extension of the user object that was stored by global const { email, info } = await checkInviteCode(inviteCode) - ctx.body = await saveUser({ - firstName, - lastName, - password, - email, - ...info, - }, info.tenantId) + ctx.body = await saveUser( + { + firstName, + lastName, + password, + email, + ...info, + }, + info.tenantId + ) // this will flesh out the body response await exports.save(ctx) } catch (err) { diff --git a/packages/worker/src/api/controllers/global/workspaces.js b/packages/worker/src/api/controllers/global/workspaces.js index fb1a859826..95a1ec296d 100644 --- a/packages/worker/src/api/controllers/global/workspaces.js +++ b/packages/worker/src/api/controllers/global/workspaces.js @@ -1,7 +1,4 @@ -const { - getWorkspaceParams, - generateWorkspaceID, -} = require("@budibase/auth/db") +const { getWorkspaceParams, generateWorkspaceID } = require("@budibase/auth/db") const { getGlobalDB } = require("@budibase/auth/tenancy") exports.save = async function (ctx) { diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js index d39546ff3a..1e2a12ab1b 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -2,7 +2,8 @@ const Router = require("@koa/router") const compress = require("koa-compress") const zlib = require("zlib") const { routes } = require("./routes") -const { buildAuthMiddleware, auditLog, buildTenancyMiddleware } = require("@budibase/auth").auth +const { buildAuthMiddleware, auditLog, buildTenancyMiddleware } = + require("@budibase/auth").auth const NO_TENANCY_ENDPOINTS = [ { @@ -12,7 +13,7 @@ const NO_TENANCY_ENDPOINTS = [ { route: "/api/global/users/self", method: "GET", - } + }, ] const PUBLIC_ENDPOINTS = [ diff --git a/packages/worker/src/constants/templates/index.js b/packages/worker/src/constants/templates/index.js index 44fe345af1..ac5427ba1f 100644 --- a/packages/worker/src/constants/templates/index.js +++ b/packages/worker/src/constants/templates/index.js @@ -6,9 +6,7 @@ const { GLOBAL_OWNER, } = require("../index") const { join } = require("path") -const { - getTemplateParams, -} = require("@budibase/auth/db") +const { getTemplateParams } = require("@budibase/auth/db") const { getGlobalDB } = require("@budibase/auth/tenancy") exports.EmailTemplates = { diff --git a/packages/worker/src/utilities/email.js b/packages/worker/src/utilities/email.js index 22de6b4d6d..c32ff05cf5 100644 --- a/packages/worker/src/utilities/email.js +++ b/packages/worker/src/utilities/email.js @@ -68,12 +68,7 @@ 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(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}` diff --git a/packages/worker/src/utilities/templates.js b/packages/worker/src/utilities/templates.js index 31d92e9226..4dee52b531 100644 --- a/packages/worker/src/utilities/templates.js +++ b/packages/worker/src/utilities/templates.js @@ -44,9 +44,7 @@ exports.getSettingsTemplateContext = async (purpose, code = null) => { case EmailTemplatePurpose.INVITATION: context[InternalTemplateBindings.INVITE_CODE] = code context[InternalTemplateBindings.INVITE_URL] = checkSlashesInUrl( - addTenantToUrl( - `${URL}/builder/invite?code=${code}` - ) + addTenantToUrl(`${URL}/builder/invite?code=${code}`) ) break } From e7974f7e868822094d65e357a2bfbc848a3e1515 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 3 Aug 2021 15:32:25 +0100 Subject: [PATCH 54/56] Implementing some changes to how context gets set for tenancy, after testing, as well as updating server. --- packages/auth/src/cache/user.js | 15 ++-- packages/auth/src/middleware/authenticated.js | 11 +-- packages/auth/src/middleware/matchers.js | 3 + packages/auth/src/middleware/tenancy.js | 20 ++---- packages/auth/src/tenancy/FunctionContext.js | 69 +++++++++++++++++++ packages/auth/src/tenancy/context.js | 49 ++++++------- packages/auth/src/tenancy/tenancy.js | 2 +- .../builder/portal/manage/email/index.svelte | 3 + .../server/src/api/controllers/application.js | 14 ++-- packages/server/src/api/index.js | 10 ++- .../src/automations/steps/sendSmtpEmail.js | 4 +- packages/server/src/automations/thread.js | 19 ++--- packages/server/src/middleware/currentapp.js | 1 + packages/server/src/utilities/global.js | 4 +- .../server/src/utilities/workerRequests.js | 9 ++- .../src/api/controllers/global/users.js | 62 ++--------------- packages/worker/src/api/index.js | 33 ++++----- packages/worker/src/api/routes/global/auth.js | 18 +++-- 18 files changed, 194 insertions(+), 152 deletions(-) create mode 100644 packages/auth/src/tenancy/FunctionContext.js diff --git a/packages/auth/src/cache/user.js b/packages/auth/src/cache/user.js index d8c67a5854..d5424d277b 100644 --- a/packages/auth/src/cache/user.js +++ b/packages/auth/src/cache/user.js @@ -1,22 +1,25 @@ const redis = require("../redis/authRedis") const { - updateTenantId, + getTenantId, lookupTenantId, getGlobalDB, - isTenantIdSet, } = require("../tenancy") const EXPIRY_SECONDS = 3600 -exports.getUser = async userId => { - if (!isTenantIdSet()) { - updateTenantId(await lookupTenantId(userId)) +exports.getUser = async (userId, tenantId = null) => { + if (!tenantId) { + try { + tenantId = getTenantId() + } catch (err) { + tenantId = await lookupTenantId(userId) + } } const client = await redis.getUserClient() // try cache let user = await client.get(userId) if (!user) { - user = await getGlobalDB().get(userId) + user = await getGlobalDB(tenantId).get(userId) client.store(userId, user, EXPIRY_SECONDS) } return user diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index cf9a19e58b..303553212b 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -3,7 +3,6 @@ const { getCookie, clearCookie } = require("../utils") const { getUser } = require("../cache/user") const { getSession, updateSessionTTL } = require("../security/sessions") const { buildMatcherRegex, matches } = require("./matchers") -const { isTenantIdSet, updateTenantId } = require("../tenancy") const env = require("../environment") function finalise( @@ -17,6 +16,11 @@ function finalise( ctx.version = version } +/** + * This middleware is tenancy aware, so that it does not depend on other middlewares being used. + * The tenancy modules should not be used here and it should be assumed that the tenancy context + * has not yet been populated. + */ module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => { const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : [] return async (ctx, next) => { @@ -42,10 +46,7 @@ module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => { error = "No session found" } else { try { - if (session.tenantId && !isTenantIdSet()) { - updateTenantId(session.tenantId) - } - user = await getUser(userId) + user = await getUser(userId, session.tenantId) delete user.password authenticated = true } catch (err) { diff --git a/packages/auth/src/middleware/matchers.js b/packages/auth/src/middleware/matchers.js index f46f0d781b..a555823136 100644 --- a/packages/auth/src/middleware/matchers.js +++ b/packages/auth/src/middleware/matchers.js @@ -1,6 +1,9 @@ const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g exports.buildMatcherRegex = patterns => { + if (!patterns) { + return [] + } return patterns.map(pattern => { const isObj = typeof pattern === "object" && pattern.route const method = isObj ? pattern.method : "GET" diff --git a/packages/auth/src/middleware/tenancy.js b/packages/auth/src/middleware/tenancy.js index 9242f11c26..b80b9a6763 100644 --- a/packages/auth/src/middleware/tenancy.js +++ b/packages/auth/src/middleware/tenancy.js @@ -1,20 +1,14 @@ -const { createTenancyContext, setTenantId } = require("../tenancy") +const { setTenantId } = require("../tenancy") +const ContextFactory = require("../tenancy/FunctionContext") const { buildMatcherRegex, matches } = require("./matchers") module.exports = (allowQueryStringPatterns, noTenancyPatterns) => { const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns) const noTenancyOptions = buildMatcherRegex(noTenancyPatterns) - return (ctx, next) => { - // always run in context - return createTenancyContext().runAndReturn(() => { - if (matches(ctx, noTenancyOptions)) { - return next() - } - - const allowQs = !!matches(ctx, allowQsOptions) - setTenantId(ctx, { allowQs }) - return next() - }) - } + return ContextFactory.getMiddleware(ctx => { + const allowNoTenant = !!matches(ctx, noTenancyOptions) + const allowQs = !!matches(ctx, allowQsOptions) + setTenantId(ctx, { allowQs, allowNoTenant }) + }) } diff --git a/packages/auth/src/tenancy/FunctionContext.js b/packages/auth/src/tenancy/FunctionContext.js new file mode 100644 index 0000000000..06e0d92f5c --- /dev/null +++ b/packages/auth/src/tenancy/FunctionContext.js @@ -0,0 +1,69 @@ +const cls = require("cls-hooked") +const { newid } = require("../hashing") + +const REQUEST_ID_KEY = "requestId" + +class FunctionContext { + static getMiddleware(updateCtxFn = null) { + const namespace = this.createNamespace() + + return async function(ctx, next) { + await new Promise(namespace.bind(function(resolve, reject) { + // store a contextual request ID that can be used anywhere (audit logs) + namespace.set(REQUEST_ID_KEY, newid()) + namespace.bindEmitter(ctx.req) + namespace.bindEmitter(ctx.res) + + if (updateCtxFn) { + updateCtxFn(ctx) + } + next().then(resolve).catch(reject) + })) + } + } + + static run(callback) { + const namespace = this.createNamespace() + + return namespace.runAndReturn(callback) + } + + static setOnContext(key, value) { + const namespace = this.createNamespace() + namespace.set(key, value) + } + + static getContextStorage() { + if (this._namespace && this._namespace.active) { + const { id, _ns_name, ...contextData } = this._namespace.active + return contextData + } + + return {} + } + + static getFromContext(key) { + const context = this.getContextStorage() + if (context) { + return context[key] + } else { + return null + } + } + + static destroyNamespace() { + if (this._namespace) { + cls.destroyNamespace("session") + this._namespace = null + } + } + + static createNamespace() { + if (!this._namespace) { + this._namespace = cls.createNamespace("session") + } + return this._namespace + } +} + +module.exports = FunctionContext diff --git a/packages/auth/src/tenancy/context.js b/packages/auth/src/tenancy/context.js index 5210700bd0..71fba2a3d5 100644 --- a/packages/auth/src/tenancy/context.js +++ b/packages/auth/src/tenancy/context.js @@ -1,6 +1,6 @@ -const cls = require("cls-hooked") const env = require("../environment") const { Headers } = require("../../constants") +const cls = require("./FunctionContext") exports.DEFAULT_TENANT_ID = "default" @@ -12,66 +12,61 @@ exports.isMultiTenant = () => { return env.MULTI_TENANCY } -// continuation local storage -const CONTEXT_NAME = "tenancy" const TENANT_ID = "tenantId" -exports.createTenancyContext = () => { - return cls.createNamespace(CONTEXT_NAME) -} - -const getTenancyContext = () => { - return cls.getNamespace(CONTEXT_NAME) -} - // used for automations, API endpoints should always be in context already exports.doInTenant = (tenantId, task) => { - const context = getTenancyContext() - return getTenancyContext().runAndReturn(() => { + return cls.run(() => { // set the tenant id - context.set(TENANT_ID, tenantId) + cls.setOnContext(TENANT_ID, tenantId) // invoke the task const result = task() // clear down the tenant id manually for extra safety // this should also happen automatically when the call exits - context.set(TENANT_ID, null) + cls.setOnContext(TENANT_ID, null) return result }) } exports.updateTenantId = tenantId => { - getTenancyContext().set(TENANT_ID, tenantId) + cls.setOnContext(TENANT_ID, tenantId) } -exports.setTenantId = (ctx, opts = { allowQs: false }) => { +exports.setTenantId = (ctx, opts = { allowQs: false, allowNoTenant: false }) => { let tenantId // exit early if not multi-tenant if (!exports.isMultiTenant()) { - getTenancyContext().set(TENANT_ID, this.DEFAULT_TENANT_ID) + cls.setOnContext(TENANT_ID, this.DEFAULT_TENANT_ID) return } - const params = ctx.request.params || {} + const allowQs = opts && opts.allowQs + const allowNoTenant = opts && opts.allowNoTenant const header = ctx.request.headers[Headers.TENANT_ID] - const user = ctx.request.user || {} - tenantId = user.tenantId || params.tenantId || header - if (opts.allowQs && !tenantId) { + const user = ctx.user || {} + if (allowQs) { const query = ctx.request.query || {} tenantId = query.tenantId } + // override query string (if allowed) by user, or header + // URL params cannot be used in a middleware, as they are + // processed later in the chain + tenantId = user.tenantId || header || tenantId - if (!tenantId) { + if (!tenantId && !allowNoTenant) { ctx.throw(403, "Tenant id not set") } - - getTenancyContext().set(TENANT_ID, tenantId) + // check tenant ID just incase no tenant was allowed + if (tenantId) { + cls.setOnContext(TENANT_ID, tenantId) + } } exports.isTenantIdSet = () => { - const tenantId = getTenancyContext().get(TENANT_ID) + const tenantId = cls.getFromContext(TENANT_ID) return !!tenantId } @@ -79,7 +74,7 @@ exports.getTenantId = () => { if (!exports.isMultiTenant()) { return exports.DEFAULT_TENANT_ID } - const tenantId = getTenancyContext().get(TENANT_ID) + const tenantId = cls.getFromContext(TENANT_ID) if (!tenantId) { throw Error("Tenant id not found") } diff --git a/packages/auth/src/tenancy/tenancy.js b/packages/auth/src/tenancy/tenancy.js index 16dedd3ebf..6e18ea7154 100644 --- a/packages/auth/src/tenancy/tenancy.js +++ b/packages/auth/src/tenancy/tenancy.js @@ -1,4 +1,4 @@ -const { getDB } = require("../../db") +const { getDB } = require("../db") const { SEPARATOR, StaticDatabases } = require("../db/constants") const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("./context") const env = require("../environment") 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 5161625510..f94d2bcd0f 100644 --- a/packages/builder/src/pages/builder/portal/manage/email/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/email/index.svelte @@ -94,6 +94,9 @@ requireAuth = smtpConfig.config.auth != null // always attach the auth for the forms purpose - // this will be removed later if required + if (!smtpDoc.config) { + smtpDoc.config = {} + } if (!smtpDoc.config.auth) { smtpConfig.config.auth = { type: "login", diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 15c4c9bf7a..d0de611d74 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -38,6 +38,7 @@ const { backupClientLibrary, revertClientLibrary, } = require("../../utilities/fileSystem/clientLibrary") +const { getTenantId, isMultiTenant } = require("@budibase/auth/tenancy") const URL_REGEX_SLASH = /\/|\\/g @@ -92,8 +93,9 @@ async function getAppUrlIfNotInUse(ctx) { return url } -async function createInstance(tenantId, template) { - const baseAppId = generateAppID(env.MULTI_TENANCY ? tenantId : null) +async function createInstance(template) { + const tenantId = isMultiTenant() ? getTenantId() : null + const baseAppId = generateAppID(tenantId) const appId = generateDevAppID(baseAppId) const db = new CouchDB(appId) @@ -128,8 +130,7 @@ async function createInstance(tenantId, 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 tenantId = ctx.user.tenantId - const apps = await getAllApps(CouchDB, { tenantId, dev, all }) + const apps = await getAllApps(CouchDB, { dev, all }) // get the locks for all the dev apps if (dev || all) { @@ -189,7 +190,6 @@ exports.fetchAppPackage = async function (ctx) { } exports.create = async function (ctx) { - const tenantId = ctx.user.tenantId const { useTemplate, templateKey } = ctx.request.body const instanceConfig = { useTemplate, @@ -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(tenantId, instanceConfig) + const instance = await createInstance(instanceConfig) const appId = instance._id const url = await getAppUrlIfNotInUse(ctx) @@ -222,7 +222,7 @@ exports.create = async function (ctx) { url: url, template: ctx.request.body.template, instance: instance, - tenantId, + tenantId: getTenantId(), updatedAt: new Date().toISOString(), createdAt: new Date().toISOString(), } diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 13fb7efd1d..81601eea1a 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -10,6 +10,13 @@ const env = require("../environment") const router = new Router() +const NO_TENANCY_ENDPOINTS = [ + { + route: "/api/analytics", + method: "GET", + }, +] + router .use( compress({ @@ -32,12 +39,13 @@ router }) .use("/health", ctx => (ctx.status = 200)) .use("/version", ctx => (ctx.body = pkg.version)) - .use(buildTenancyMiddleware()) .use( buildAuthMiddleware(null, { publicAllowed: true, }) ) + // nothing in the server should allow query string tenants + .use(buildTenancyMiddleware(null, NO_TENANCY_ENDPOINTS)) .use(currentApp) .use(auditLog) diff --git a/packages/server/src/automations/steps/sendSmtpEmail.js b/packages/server/src/automations/steps/sendSmtpEmail.js index 7b25da801e..764972b402 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, tenantId }) { +module.exports.run = async function ({ inputs }) { let { to, from, subject, contents } = inputs if (!contents) { contents = "

No content

" } try { - let response = await sendSmtpEmail(tenantId, to, from, subject, contents) + let response = await sendSmtpEmail(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 4dc843538b..aada0ca0ca 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -6,6 +6,7 @@ const { processObject } = require("@budibase/string-templates") const { DEFAULT_TENANT_ID } = require("@budibase/auth").constants const CouchDB = require("../db") const { DocumentTypes } = require("../db/utils") +const { doInTenant } = require("@budibase/auth/tenancy") const FILTER_STEP_ID = logic.BUILTIN_DEFINITIONS.FILTER.stepId @@ -56,7 +57,7 @@ class Orchestrator { async execute() { let automation = this._automation - const app = this.getApp() + const app = await this.getApp() for (let step of automation.definition.steps) { let stepFn = await this.getStepFunctionality(step.type, step.stepId) step.inputs = await processObject(step.inputs, this._context) @@ -66,13 +67,15 @@ class Orchestrator { ) // appId is always passed try { - const outputs = await stepFn({ - inputs: step.inputs, - appId: this._appId, - apiKey: automation.apiKey, - emitter: this._emitter, - context: this._context, - tenantId: app.tenantId || DEFAULT_TENANT_ID, + let tenantId = app.tenantId || DEFAULT_TENANT_ID + const outputs = await doInTenant(tenantId, () => { + return stepFn({ + inputs: step.inputs, + appId: this._appId, + apiKey: automation.apiKey, + emitter: this._emitter, + context: this._context, + }) }) if (step.stepId === FILTER_STEP_ID && !outputs.success) { break diff --git a/packages/server/src/middleware/currentapp.js b/packages/server/src/middleware/currentapp.js index 7169a36320..8f2403cc37 100644 --- a/packages/server/src/middleware/currentapp.js +++ b/packages/server/src/middleware/currentapp.js @@ -68,5 +68,6 @@ module.exports = async (ctx, next) => { ) { setCookie(ctx, { appId }, Cookies.CurrentApp) } + return next() } diff --git a/packages/server/src/utilities/global.js b/packages/server/src/utilities/global.js index d3e9701b62..2757398586 100644 --- a/packages/server/src/utilities/global.js +++ b/packages/server/src/utilities/global.js @@ -33,7 +33,9 @@ function processUser(appId, user) { } exports.getCachedSelf = async (ctx, appId) => { - const user = await userCache.getUser(ctx.user._id, ctx.user.tenantId) + // this has to be tenant aware, can't depend on the context to find it out + // running some middlewares before the tenancy causes context to break + const user = await userCache.getUser(ctx.user._id) return processUser(appId, user) } diff --git a/packages/server/src/utilities/workerRequests.js b/packages/server/src/utilities/workerRequests.js index 215a453bfb..066f6e23d4 100644 --- a/packages/server/src/utilities/workerRequests.js +++ b/packages/server/src/utilities/workerRequests.js @@ -4,6 +4,7 @@ const { checkSlashesInUrl } = require("./index") const { getDeployedAppID } = require("@budibase/auth/db") const { updateAppRole, getGlobalUser } = require("./global") const { Headers } = require("@budibase/auth/constants") +const { getTenantId, isTenantIdSet } = require("@budibase/auth/tenancy") function request(ctx, request) { if (!request.headers) { @@ -11,6 +12,9 @@ function request(ctx, request) { } if (!ctx) { request.headers[Headers.API_KEY] = env.INTERNAL_API_KEY + if (isTenantIdSet()) { + request.headers[Headers.TENANT_ID] = getTenantId() + } } if (request.body && Object.keys(request.body).length > 0) { request.headers["Content-Type"] = "application/json" @@ -29,13 +33,14 @@ function request(ctx, request) { exports.request = request -exports.sendSmtpEmail = async (tenantId, to, from, subject, contents) => { +// have to pass in the tenant ID as this could be coming from an automation +exports.sendSmtpEmail = async (to, from, subject, contents) => { + // tenant ID will be set in header const response = await fetch( checkSlashesInUrl(env.WORKER_URL + `/api/global/email/send`), request(null, { method: "POST", body: { - tenantId, email: to, from, contents, diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index e2284b09a8..0ba4dd7986 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -13,65 +13,9 @@ const { user: userCache } = require("@budibase/auth/cache") const { invalidateSessions } = require("@budibase/auth/sessions") const CouchDB = require("../../../db") const env = require("../../../environment") -const { getGlobalDB, getTenantId } = require("@budibase/auth/tenancy") +const { getGlobalDB, getTenantId, doesTenantExist, tryAddTenant, updateTenantId } = require("@budibase/auth/tenancy") 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 => { - if (!id) { - return null - } - try { - return await db.get(id) - } catch (err) { - return { _id: id } - } - } - 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) - promises.push(db.put(tenants)) - } - await Promise.all(promises) -} - -async function doesTenantExist(tenantId) { - const db = new CouchDB(PLATFORM_INFO_DB) - let tenants - try { - tenants = await db.get(TENANT_DOC) - } catch (err) { - // if theres an error the doc doesn't exist, no tenants exist - return false - } - return ( - tenants && - Array.isArray(tenants.tenantIds) && - tenants.tenantIds.indexOf(tenantId) !== -1 - ) -} async function allUsers() { const db = getGlobalDB() @@ -87,6 +31,8 @@ async function saveUser(user, tenantId) { if (!tenantId) { throw "No tenancy specified." } + // need to set the context for this request, as specified + updateTenantId(tenantId) // specify the tenancy incase we're making a new admin user (public) const db = getGlobalDB(tenantId) let { email, password, _id } = user @@ -162,7 +108,7 @@ exports.adminUser = async ctx => { ctx.throw(403, "Organisation already exists.") } - const db = getGlobalDB() + const db = getGlobalDB(tenantId) const response = await db.allDocs( getGlobalUserParams(null, { include_docs: true, diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js index 1e2a12ab1b..844690148f 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -5,17 +5,6 @@ const { routes } = require("./routes") const { buildAuthMiddleware, auditLog, buildTenancyMiddleware } = require("@budibase/auth").auth -const NO_TENANCY_ENDPOINTS = [ - { - route: "/api/system", - method: "ALL", - }, - { - route: "/api/global/users/self", - method: "GET", - }, -] - const PUBLIC_ENDPOINTS = [ { // this covers all of the POST auth routes @@ -32,10 +21,6 @@ const PUBLIC_ENDPOINTS = [ route: "/api/global/configs/public", method: "GET", }, - { - route: "api/global/flags", - method: "GET", - }, { route: "/api/global/configs/checklist", method: "GET", @@ -48,6 +33,22 @@ const PUBLIC_ENDPOINTS = [ route: "/api/global/users/invite/accept", method: "POST", }, + { + route: "api/system/flags", + method: "GET", + }, +] + +const NO_TENANCY_ENDPOINTS = [ + ...PUBLIC_ENDPOINTS, + { + route: "/api/system", + method: "ALL", + }, + { + route: "/api/global/users/self", + method: "GET", + }, ] const router = new Router() @@ -65,8 +66,8 @@ router }) ) .use("/health", ctx => (ctx.status = 200)) - .use(buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_ENDPOINTS)) .use(buildAuthMiddleware(PUBLIC_ENDPOINTS)) + .use(buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_ENDPOINTS)) // for now no public access is allowed to worker (bar health check) .use((ctx, next) => { if (!ctx.isAuthenticated && !ctx.publicEndpoint) { diff --git a/packages/worker/src/api/routes/global/auth.js b/packages/worker/src/api/routes/global/auth.js index 34260dee7f..bf9ebfcb7c 100644 --- a/packages/worker/src/api/routes/global/auth.js +++ b/packages/worker/src/api/routes/global/auth.js @@ -2,6 +2,7 @@ const Router = require("@koa/router") const authController = require("../../controllers/global/auth") const joiValidator = require("../../../middleware/joi-validator") const Joi = require("joi") +const { updateTenantId } = require("@budibase/auth/tenancy") const router = Router() @@ -28,34 +29,41 @@ function buildResetUpdateValidation() { }).required().unknown(false)) } +function updateTenant(ctx, next) { + updateTenantId(ctx.params.tenantId) + return next() +} + router .post( "/api/global/auth/:tenantId/login", buildAuthValidation(), + updateTenant, authController.authenticate ) .post( "/api/global/auth/:tenantId/reset", buildResetValidation(), + updateTenant, authController.reset ) .post( "/api/global/auth/:tenantId/reset/update", buildResetUpdateValidation(), + updateTenant, authController.resetUpdate ) .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/global/auth/:tenantId/google", updateTenant, authController.googlePreAuth) + .get("/api/global/auth/:tenantId/google/callback", updateTenant, authController.googleAuth) .get( "/api/global/auth/:tenantId/oidc/configs/:configId", + updateTenant, authController.oidcPreAuth ) - .get("/api/global/auth/:tenantId/oidc/callback", authController.oidcAuth) + .get("/api/global/auth/:tenantId/oidc/callback", updateTenant, authController.oidcAuth) // deprecated - used by the default system before tenancy .get("/api/admin/auth/google/callback", authController.googleAuth) - .get("/api/global/auth/google/callback", authController.googleAuth) .get("/api/admin/auth/oidc/callback", authController.oidcAuth) - .get("/api/global/auth/oidc/callback", authController.oidcAuth) module.exports = router From 88c07d28eba54941c5b16c3ea303770ee844301a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 3 Aug 2021 15:41:51 +0100 Subject: [PATCH 55/56] Linting. --- packages/auth/src/cache/user.js | 6 +--- packages/auth/src/tenancy/FunctionContext.js | 28 +++++++++++-------- packages/auth/src/tenancy/context.js | 5 +++- .../src/api/controllers/global/users.js | 8 +++++- packages/worker/src/api/routes/global/auth.js | 18 ++++++++++-- 5 files changed, 43 insertions(+), 22 deletions(-) diff --git a/packages/auth/src/cache/user.js b/packages/auth/src/cache/user.js index d5424d277b..4a19da489f 100644 --- a/packages/auth/src/cache/user.js +++ b/packages/auth/src/cache/user.js @@ -1,9 +1,5 @@ const redis = require("../redis/authRedis") -const { - getTenantId, - lookupTenantId, - getGlobalDB, -} = require("../tenancy") +const { getTenantId, lookupTenantId, getGlobalDB } = require("../tenancy") const EXPIRY_SECONDS = 3600 diff --git a/packages/auth/src/tenancy/FunctionContext.js b/packages/auth/src/tenancy/FunctionContext.js index 06e0d92f5c..d97a3a30b4 100644 --- a/packages/auth/src/tenancy/FunctionContext.js +++ b/packages/auth/src/tenancy/FunctionContext.js @@ -7,18 +7,20 @@ class FunctionContext { static getMiddleware(updateCtxFn = null) { const namespace = this.createNamespace() - return async function(ctx, next) { - await new Promise(namespace.bind(function(resolve, reject) { - // store a contextual request ID that can be used anywhere (audit logs) - namespace.set(REQUEST_ID_KEY, newid()) - namespace.bindEmitter(ctx.req) - namespace.bindEmitter(ctx.res) + return async function (ctx, next) { + await new Promise( + namespace.bind(function (resolve, reject) { + // store a contextual request ID that can be used anywhere (audit logs) + namespace.set(REQUEST_ID_KEY, newid()) + namespace.bindEmitter(ctx.req) + namespace.bindEmitter(ctx.res) - if (updateCtxFn) { - updateCtxFn(ctx) - } - next().then(resolve).catch(reject) - })) + if (updateCtxFn) { + updateCtxFn(ctx) + } + next().then(resolve).catch(reject) + }) + ) } } @@ -35,7 +37,9 @@ class FunctionContext { static getContextStorage() { if (this._namespace && this._namespace.active) { - const { id, _ns_name, ...contextData } = this._namespace.active + let contextData = this._namespace.active + delete contextData.id + delete contextData._ns_name return contextData } diff --git a/packages/auth/src/tenancy/context.js b/packages/auth/src/tenancy/context.js index 71fba2a3d5..aa63309928 100644 --- a/packages/auth/src/tenancy/context.js +++ b/packages/auth/src/tenancy/context.js @@ -35,7 +35,10 @@ exports.updateTenantId = tenantId => { cls.setOnContext(TENANT_ID, tenantId) } -exports.setTenantId = (ctx, opts = { allowQs: false, allowNoTenant: false }) => { +exports.setTenantId = ( + ctx, + opts = { allowQs: false, allowNoTenant: false } +) => { let tenantId // exit early if not multi-tenant if (!exports.isMultiTenant()) { diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index 0ba4dd7986..f43195a1de 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -13,7 +13,13 @@ const { user: userCache } = require("@budibase/auth/cache") const { invalidateSessions } = require("@budibase/auth/sessions") const CouchDB = require("../../../db") const env = require("../../../environment") -const { getGlobalDB, getTenantId, doesTenantExist, tryAddTenant, updateTenantId } = require("@budibase/auth/tenancy") +const { + getGlobalDB, + getTenantId, + doesTenantExist, + tryAddTenant, + updateTenantId, +} = require("@budibase/auth/tenancy") const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name diff --git a/packages/worker/src/api/routes/global/auth.js b/packages/worker/src/api/routes/global/auth.js index bf9ebfcb7c..f85d08057b 100644 --- a/packages/worker/src/api/routes/global/auth.js +++ b/packages/worker/src/api/routes/global/auth.js @@ -54,14 +54,26 @@ router authController.resetUpdate ) .post("/api/global/auth/logout", authController.logout) - .get("/api/global/auth/:tenantId/google", updateTenant, authController.googlePreAuth) - .get("/api/global/auth/:tenantId/google/callback", updateTenant, authController.googleAuth) + .get( + "/api/global/auth/:tenantId/google", + updateTenant, + authController.googlePreAuth + ) + .get( + "/api/global/auth/:tenantId/google/callback", + updateTenant, + authController.googleAuth + ) .get( "/api/global/auth/:tenantId/oidc/configs/:configId", updateTenant, authController.oidcPreAuth ) - .get("/api/global/auth/:tenantId/oidc/callback", updateTenant, authController.oidcAuth) + .get( + "/api/global/auth/:tenantId/oidc/callback", + updateTenant, + authController.oidcAuth + ) // deprecated - used by the default system before tenancy .get("/api/admin/auth/google/callback", authController.googleAuth) .get("/api/admin/auth/oidc/callback", authController.oidcAuth) From 4e13dde65748826b5c5881f68ff915b6e9ac9878 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 3 Aug 2021 17:14:17 +0100 Subject: [PATCH 56/56] Fixing some issues highlighted by worker test cases. --- packages/auth/src/tenancy/context.js | 4 ---- packages/worker/src/api/controllers/global/users.js | 3 +-- .../src/api/routes/tests/utilities/TestConfiguration.js | 9 +++++++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/auth/src/tenancy/context.js b/packages/auth/src/tenancy/context.js index aa63309928..f3f1f541e9 100644 --- a/packages/auth/src/tenancy/context.js +++ b/packages/auth/src/tenancy/context.js @@ -23,10 +23,6 @@ exports.doInTenant = (tenantId, task) => { // invoke the task const result = task() - // clear down the tenant id manually for extra safety - // this should also happen automatically when the call exits - cls.setOnContext(TENANT_ID, null) - return result }) } diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index f43195a1de..24b00fe3a6 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -268,6 +268,7 @@ exports.invite = async ctx => { if (!userInfo) { userInfo = {} } + userInfo.tenantId = getTenantId() await sendEmail(email, EmailTemplatePurpose.INVITATION, { subject: "{{ company }} platform invitation", info: userInfo, @@ -292,8 +293,6 @@ exports.inviteAccept = async ctx => { }, info.tenantId ) - // this will flesh out the body response - await exports.save(ctx) } catch (err) { ctx.throw(400, "Unable to create new user, invitation invalid.") } diff --git a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js index 3bc9c397d3..7f84de6b7d 100644 --- a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js @@ -10,6 +10,7 @@ const { newid } = require("../../../../../../auth/src/hashing") const { TENANT_ID } = require("./structures") const auth = require("@budibase/auth") const CouchDB = require("../../../../db") +const { doInTenant } = require("@budibase/auth/tenancy") auth.init(CouchDB) class TestConfiguration { @@ -40,7 +41,9 @@ class TestConfiguration { if (params) { request.params = params } - await controlFunc(request) + await doInTenant(TENANT_ID, () => { + return controlFunc(request) + }) return request.body } @@ -96,7 +99,9 @@ class TestConfiguration { } async getUser(email) { - return getGlobalUserByEmail(email, TENANT_ID) + return doInTenant(TENANT_ID, () => { + return getGlobalUserByEmail(email) + }) } async createUser(email = "test@test.com", password = "test") {