From f3156fca06129d98440a5ba794b47d6b7e69b046 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 16 Jul 2021 18:04:49 +0100 Subject: [PATCH] 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)