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) => {
|
exports.getUser = async (userId, tenantId = null) => {
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
tenantId = await lookupTenantId({ userId })
|
tenantId = await lookupTenantId(userId)
|
||||||
}
|
}
|
||||||
const client = await redis.getUserClient()
|
const client = await redis.getUserClient()
|
||||||
// try cache
|
// try cache
|
||||||
|
|
|
@ -4,6 +4,7 @@ const { getDB } = require("./index")
|
||||||
|
|
||||||
const UNICODE_MAX = "\ufff0"
|
const UNICODE_MAX = "\ufff0"
|
||||||
const SEPARATOR = "_"
|
const SEPARATOR = "_"
|
||||||
|
const DEFAULT_TENANT = "default"
|
||||||
|
|
||||||
exports.ViewNames = {
|
exports.ViewNames = {
|
||||||
USER_BY_EMAIL: "by_email",
|
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.
|
* Gets the name of the global DB to connect to in a multi-tenancy system.
|
||||||
*/
|
*/
|
||||||
exports.getGlobalDB = tenantId => {
|
exports.getGlobalDB = tenantId => {
|
||||||
const globalName = exports.StaticDatabases.GLOBAL.name
|
|
||||||
// fallback for system pre multi-tenancy
|
// fallback for system pre multi-tenancy
|
||||||
if (!tenantId) {
|
let dbName = exports.StaticDatabases.GLOBAL.name
|
||||||
return globalName
|
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.
|
* Given a koa context this tries to find the correct tenant Global DB.
|
||||||
*/
|
*/
|
||||||
exports.getGlobalDBFromCtx = ctx => {
|
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 passport = require("koa-passport")
|
||||||
const LocalStrategy = require("passport-local").Strategy
|
const LocalStrategy = require("passport-local").Strategy
|
||||||
const JwtStrategy = require("passport-jwt").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 { jwt, local, authenticated, google, auditLog } = require("./middleware")
|
||||||
const { setDB } = require("./db")
|
const { setDB } = require("./db")
|
||||||
const userCache = require("./cache/user")
|
const userCache = require("./cache/user")
|
||||||
|
|
|
@ -13,7 +13,7 @@ const { lookupTenantId } = require("../../utils")
|
||||||
async function authenticate(token, tokenSecret, profile, done) {
|
async function authenticate(token, tokenSecret, profile, done) {
|
||||||
// Check the user exists in the instance DB by email
|
// Check the user exists in the instance DB by email
|
||||||
const userId = generateGlobalUserID(profile.id)
|
const userId = generateGlobalUserID(profile.id)
|
||||||
const tenantId = await lookupTenantId({ userId })
|
const tenantId = await lookupTenantId(userId)
|
||||||
const db = getGlobalDB(tenantId)
|
const db = getGlobalDB(tenantId)
|
||||||
|
|
||||||
let dbUser
|
let dbUser
|
||||||
|
|
|
@ -8,20 +8,27 @@ const { createASession } = require("../../security/sessions")
|
||||||
|
|
||||||
const INVALID_ERR = "Invalid Credentials"
|
const INVALID_ERR = "Invalid Credentials"
|
||||||
|
|
||||||
exports.options = {}
|
exports.options = {
|
||||||
|
passReqToCallback: true,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Passport Local Authentication Middleware.
|
* Passport Local Authentication Middleware.
|
||||||
* @param {*} email - username to login with
|
* @param {*} ctx the request structure
|
||||||
* @param {*} password - plain text password to log in with
|
* @param {*} email username to login with
|
||||||
* @param {*} done - callback from passport to return user information and errors
|
* @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
|
* @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 (!email) return done(null, false, "Email Required.")
|
||||||
if (!password) return done(null, false, "Password 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) {
|
if (dbUser == null) {
|
||||||
return done(null, false, { message: "User not found" })
|
return done(null, false, { message: "User not found" })
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,10 +101,9 @@ exports.isClient = ctx => {
|
||||||
return ctx.headers["x-budibase-type"] === "client"
|
return ctx.headers["x-budibase-type"] === "client"
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.lookupTenantId = async ({ email, userId }) => {
|
exports.lookupTenantId = async userId => {
|
||||||
const toQuery = email || userId
|
|
||||||
const db = getDB(StaticDatabases.PLATFORM_INFO.name)
|
const db = getDB(StaticDatabases.PLATFORM_INFO.name)
|
||||||
const doc = await db.get(toQuery)
|
const doc = await db.get(userId)
|
||||||
if (!doc || !doc.tenantId) {
|
if (!doc || !doc.tenantId) {
|
||||||
throw "Unable to find tenant"
|
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
|
* @param {string|null} tenantId If tenant ID is known it can be specified
|
||||||
* @return {Promise<object|null>}
|
* @return {Promise<object|null>}
|
||||||
*/
|
*/
|
||||||
exports.getGlobalUserByEmail = async (email, tenantId = null) => {
|
exports.getGlobalUserByEmail = async (email, tenantId) => {
|
||||||
if (email == null) {
|
if (email == null) {
|
||||||
throw "Must supply an email address to view"
|
throw "Must supply an email address to view"
|
||||||
}
|
}
|
||||||
if (!tenantId) {
|
|
||||||
tenantId = await exports.lookupTenantId({ email })
|
|
||||||
}
|
|
||||||
const db = getGlobalDB(tenantId)
|
const db = getGlobalDB(tenantId)
|
||||||
try {
|
try {
|
||||||
let users = (
|
let users = (
|
||||||
|
@ -138,7 +134,7 @@ exports.getGlobalUserByEmail = async (email, tenantId = null) => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err != null && err.name === "not_found") {
|
if (err != null && err.name === "not_found") {
|
||||||
await createUserEmailView(db)
|
await createUserEmailView(db)
|
||||||
return exports.getGlobalUserByEmail(email)
|
return exports.getGlobalUserByEmail(email, tenantId)
|
||||||
} else {
|
} else {
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
import { ActionButton } from "@budibase/bbui"
|
import { ActionButton } from "@budibase/bbui"
|
||||||
import GoogleLogo from "assets/google-logo.png"
|
import GoogleLogo from "assets/google-logo.png"
|
||||||
import { admin } from "stores/portal"
|
import { admin, auth } from "stores/portal"
|
||||||
|
|
||||||
let show = false
|
let show = false
|
||||||
|
|
||||||
$: show = $admin.checklist?.oauth
|
$: show = $admin.checklist?.oauth
|
||||||
|
$: tenantId = $auth.tenantId
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if show}
|
{#if show}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
on:click={() => window.open("/api/admin/auth/google", "_blank")}
|
on:click={() => window.open(`/api/admin/auth/${tenantId}/google`, "_blank")}
|
||||||
>
|
>
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<img src={GoogleLogo} alt="google icon" />
|
<img src={GoogleLogo} alt="google icon" />
|
||||||
|
|
|
@ -7,6 +7,7 @@ export function createAuthStore() {
|
||||||
let initials = null
|
let initials = null
|
||||||
let isAdmin = false
|
let isAdmin = false
|
||||||
let isBuilder = false
|
let isBuilder = false
|
||||||
|
let tenantId = "default"
|
||||||
if ($user) {
|
if ($user) {
|
||||||
if ($user.firstName) {
|
if ($user.firstName) {
|
||||||
initials = $user.firstName[0]
|
initials = $user.firstName[0]
|
||||||
|
@ -20,18 +21,21 @@ export function createAuthStore() {
|
||||||
}
|
}
|
||||||
isAdmin = !!$user.admin?.global
|
isAdmin = !!$user.admin?.global
|
||||||
isBuilder = !!$user.builder?.global
|
isBuilder = !!$user.builder?.global
|
||||||
|
tenantId = $user.tenantId || "default"
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
user: $user,
|
user: $user,
|
||||||
initials,
|
initials,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isBuilder,
|
isBuilder,
|
||||||
|
tenantId,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
checkAuth: async () => {
|
checkAuth: async () => {
|
||||||
|
|
||||||
const response = await api.get("/api/admin/users/self")
|
const response = await api.get("/api/admin/users/self")
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
user.set(null)
|
user.set(null)
|
||||||
|
@ -41,7 +45,8 @@ export function createAuthStore() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
login: async creds => {
|
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()
|
const json = await response.json()
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
user.set(json.user)
|
user.set(json.user)
|
||||||
|
@ -68,7 +73,8 @@ export function createAuthStore() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
forgotPassword: async email => {
|
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,
|
email,
|
||||||
})
|
})
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
|
@ -77,7 +83,8 @@ export function createAuthStore() {
|
||||||
await response.json()
|
await response.json()
|
||||||
},
|
},
|
||||||
resetPassword: async (password, code) => {
|
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,
|
password,
|
||||||
resetCode: code,
|
resetCode: code,
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { writable, get } from "svelte/store"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
platformUrl: "http://localhost:1000",
|
platformUrl: "http://localhost:10000",
|
||||||
logoUrl: undefined,
|
logoUrl: undefined,
|
||||||
docsUrl: undefined,
|
docsUrl: undefined,
|
||||||
company: "Budibase",
|
company: "Budibase",
|
||||||
|
|
|
@ -23,7 +23,7 @@ const { user: userCache } = require("@budibase/auth/cache")
|
||||||
const GLOBAL_USER_ID = "us_uuid1"
|
const GLOBAL_USER_ID = "us_uuid1"
|
||||||
const EMAIL = "babs@babs.com"
|
const EMAIL = "babs@babs.com"
|
||||||
const PASSWORD = "babs_password"
|
const PASSWORD = "babs_password"
|
||||||
const TENANT_ID = "tenant1"
|
const TENANT_ID = "default"
|
||||||
|
|
||||||
class TestConfiguration {
|
class TestConfiguration {
|
||||||
constructor(openServer = true) {
|
constructor(openServer = true) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ const {
|
||||||
getGlobalUserParams,
|
getGlobalUserParams,
|
||||||
getScopedFullConfig,
|
getScopedFullConfig,
|
||||||
getGlobalDBFromCtx,
|
getGlobalDBFromCtx,
|
||||||
|
getGlobalDB,
|
||||||
getAllApps,
|
getAllApps,
|
||||||
} = require("@budibase/auth/db")
|
} = require("@budibase/auth/db")
|
||||||
const { Configs } = require("../../../constants")
|
const { Configs } = require("../../../constants")
|
||||||
|
@ -168,7 +169,8 @@ exports.destroy = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.configChecklist = 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 {
|
try {
|
||||||
// TODO: Watch get started video
|
// TODO: Watch get started video
|
||||||
|
|
|
@ -56,7 +56,7 @@ async function saveUser(user, tenantId) {
|
||||||
// make sure another user isn't using the same email
|
// make sure another user isn't using the same email
|
||||||
let dbUser
|
let dbUser
|
||||||
if (email) {
|
if (email) {
|
||||||
dbUser = await getGlobalUserByEmail(email)
|
dbUser = await getGlobalUserByEmail(email, tenantId)
|
||||||
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
|
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
|
||||||
throw "Email address already in use."
|
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 CouchDB = require("../../db")
|
||||||
|
|
||||||
const APP_PREFIX = "app_"
|
|
||||||
const URL_REGEX_SLASH = /\/|\\/g
|
const URL_REGEX_SLASH = /\/|\\/g
|
||||||
|
|
||||||
exports.getApps = async ctx => {
|
exports.getApps = async ctx => {
|
||||||
// allDbs call of CouchDB is very inaccurate in production
|
const apps = await getAllApps({ CouchDB })
|
||||||
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 Promise.allSettled(appPromises)
|
|
||||||
const body = {}
|
const body = {}
|
||||||
for (let app of apps) {
|
for (let app of apps) {
|
||||||
if (app.status !== "fulfilled") {
|
if (app.status !== "fulfilled") {
|
||||||
|
|
|
@ -14,29 +14,29 @@ const PUBLIC_ENDPOINTS = [
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
route: "/api/admin/auth",
|
route: "/api/admin/auth/:tenantId/login",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
route: "/api/admin/auth/google",
|
route: "/api/admin/auth/:tenantId/google",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
route: "/api/admin/auth/google/callback",
|
route: "/api/admin/auth/:tenantId/google/callback",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
route: "/api/admin/auth/reset",
|
route: "/api/admin/auth/:tenantId/reset",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: "/api/admin/auth/:tenantId/reset/update",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
route: "/api/admin/configs/checklist",
|
route: "/api/admin/configs/checklist",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
route: "/api/apps",
|
|
||||||
method: "GET",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
route: "/api/admin/configs/public",
|
route: "/api/admin/configs/public",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|
|
@ -29,7 +29,7 @@ function buildResetUpdateValidation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
router
|
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", buildResetValidation(), authController.reset)
|
||||||
.post(
|
.post(
|
||||||
"/api/admin/auth/:tenantId/reset/update",
|
"/api/admin/auth/:tenantId/reset/update",
|
||||||
|
|
|
@ -7,6 +7,8 @@ const { Configs, LOGO_URL } = require("../../../../constants")
|
||||||
const { getGlobalUserByEmail } = require("@budibase/auth").utils
|
const { getGlobalUserByEmail } = require("@budibase/auth").utils
|
||||||
const { createASession } = require("@budibase/auth/sessions")
|
const { createASession } = require("@budibase/auth/sessions")
|
||||||
|
|
||||||
|
const TENANT_ID = "default"
|
||||||
|
|
||||||
class TestConfiguration {
|
class TestConfiguration {
|
||||||
constructor(openServer = true) {
|
constructor(openServer = true) {
|
||||||
if (openServer) {
|
if (openServer) {
|
||||||
|
@ -72,6 +74,7 @@ class TestConfiguration {
|
||||||
_id: "us_uuid1",
|
_id: "us_uuid1",
|
||||||
userId: "us_uuid1",
|
userId: "us_uuid1",
|
||||||
sessionId: "sessionid",
|
sessionId: "sessionid",
|
||||||
|
tenantId: TENANT_ID,
|
||||||
}
|
}
|
||||||
const authToken = jwt.sign(user, env.JWT_SECRET)
|
const authToken = jwt.sign(user, env.JWT_SECRET)
|
||||||
return {
|
return {
|
||||||
|
@ -81,7 +84,7 @@ class TestConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser(email) {
|
async getUser(email) {
|
||||||
return getGlobalUserByEmail(email)
|
return getGlobalUserByEmail(email, TENANT_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
async createUser(email = "test@test.com", password = "test") {
|
async createUser(email = "test@test.com", password = "test") {
|
||||||
|
|
Loading…
Reference in New Issue