Updating auth to utilise the tenant system.
This commit is contained in:
parent
912659a8ad
commit
b7995dd61d
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" })
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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") {
|
||||
|
|
Loading…
Reference in New Issue