Updating auth to utilise the tenant system.

This commit is contained in:
mike12345567 2021-07-16 15:08:58 +01:00
parent 912659a8ad
commit b7995dd61d
16 changed files with 61 additions and 50 deletions

View File

@ -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

View File

@ -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)
}
/**

View File

@ -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")

View File

@ -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

View File

@ -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" })
}

View File

@ -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<object|null>}
*/
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
}

View File

@ -1,16 +1,17 @@
<script>
import { ActionButton } from "@budibase/bbui"
import GoogleLogo from "assets/google-logo.png"
import { admin } from "stores/portal"
import { admin, auth } from "stores/portal"
let show = false
$: show = $admin.checklist?.oauth
$: tenantId = $auth.tenantId
</script>
{#if show}
<ActionButton
on:click={() => window.open("/api/admin/auth/google", "_blank")}
on:click={() => window.open(`/api/admin/auth/${tenantId}/google`, "_blank")}
>
<div class="inner">
<img src={GoogleLogo} alt="google icon" />

View File

@ -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,
})

View File

@ -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",

View File

@ -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) {

View File

@ -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

View File

@ -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."
}

View File

@ -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") {

View File

@ -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",

View File

@ -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",

View File

@ -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") {