Further work, tenancy now working but some more work to be done.
This commit is contained in:
parent
b7995dd61d
commit
f3156fca06
|
@ -21,3 +21,5 @@ exports.Configs = {
|
|||
SMTP: "smtp",
|
||||
GOOGLE: "google",
|
||||
}
|
||||
|
||||
exports.DEFAULT_TENANT_ID = "default"
|
||||
|
|
|
@ -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<object[]>} 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)
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
</Body>
|
||||
</Layout>
|
||||
<Layout gap="XS" noPadding>
|
||||
<Input label="Organisation" bind:value={adminUser.tenantId} />
|
||||
<Input label="Email" bind:value={adminUser.email} />
|
||||
<PasswordRepeatInput bind:password={adminUser.password} bind:error />
|
||||
</Layout>
|
||||
|
|
|
@ -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 @@
|
|||
<Divider noGrid />
|
||||
<Layout gap="XS" noPadding>
|
||||
<Body size="S" textAlign="center">Sign in with email</Body>
|
||||
<Input label="Organisation" bind:value={tenantId} />
|
||||
<Input label="Email" bind:value={username} />
|
||||
<Input
|
||||
label="Password"
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<script>
|
||||
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()
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
<div class="login">
|
||||
<div class="main">
|
||||
<Layout>
|
||||
<Layout noPadding justifyItems="center">
|
||||
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
||||
<Heading>Sign in to {company}</Heading>
|
||||
</Layout>
|
||||
<GoogleButton />
|
||||
<Divider noGrid />
|
||||
<Layout gap="XS" noPadding>
|
||||
<Body size="S" textAlign="center">Sign in with email</Body>
|
||||
<Input label="Organisation" bind:value={tenantId} />
|
||||
</Layout>
|
||||
<Layout gap="XS" noPadding>
|
||||
<Button cta on:click={login}>Sign in to {company}</Button>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.login {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 48px;
|
||||
}
|
||||
</style>
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -11,6 +11,7 @@ function buildAdminInitValidation() {
|
|||
Joi.object({
|
||||
email: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
tenantId: Joi.string().required(),
|
||||
})
|
||||
.required()
|
||||
.unknown(false)
|
||||
|
|
Loading…
Reference in New Issue