Don't let user exist in multiple tenants when using custom sso

This commit is contained in:
Rory Powell 2021-10-06 15:15:46 +01:00
parent 09b0dbe868
commit f10297953f
4 changed files with 128 additions and 112 deletions

View File

@ -1,6 +1,7 @@
const env = require("../../environment") const env = require("../../environment")
const jwt = require("jsonwebtoken") const jwt = require("jsonwebtoken")
const { generateGlobalUserID } = require("../../db/utils") const { generateGlobalUserID } = require("../../db/utils")
const { saveUser } = require("../../utils")
const { authError } = require("./utils") const { authError } = require("./utils")
const { newid } = require("../../hashing") const { newid } = require("../../hashing")
const { createASession } = require("../../security/sessions") const { createASession } = require("../../security/sessions")
@ -71,7 +72,13 @@ exports.authenticateThirdParty = async function (
dbUser = await syncUser(dbUser, thirdPartyUser) dbUser = await syncUser(dbUser, thirdPartyUser)
// create or sync the user // create or sync the user
const response = await db.put(dbUser) let response
try {
response = await saveUser(dbUser, getTenantId(), false, false)
} catch (err) {
return authError(done, err)
}
dbUser._rev = response.rev dbUser._rev = response.rev
// authenticate // authenticate

View File

@ -107,3 +107,13 @@ exports.lookupTenantId = async userId => {
} }
return tenantId return tenantId
} }
// lookup, could be email or userId, either will return a doc
exports.getTenantUser = async identifier => {
const db = getDB(PLATFORM_INFO_DB)
try {
return await db.get(identifier)
} catch (err) {
return null
}
}

View File

@ -1,10 +1,24 @@
const { DocumentTypes, SEPARATOR, ViewNames } = require("./db/utils") const {
DocumentTypes,
SEPARATOR,
ViewNames,
generateGlobalUserID,
} = require("./db/utils")
const jwt = require("jsonwebtoken") const jwt = require("jsonwebtoken")
const { options } = require("./middleware/passport/jwt") const { options } = require("./middleware/passport/jwt")
const { createUserEmailView } = require("./db/views") const { createUserEmailView } = require("./db/views")
const { Headers } = require("./constants") const { Headers, UserStatus } = require("./constants")
const { getGlobalDB } = require("./tenancy") const {
getGlobalDB,
updateTenantId,
getTenantUser,
tryAddTenant,
} = require("./tenancy")
const environment = require("./environment") const environment = require("./environment")
const accounts = require("./cloud/accounts")
const { hash } = require("./hashing")
const userCache = require("./cache/user")
const env = require("./environment")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR const APP_PREFIX = DocumentTypes.APP + SEPARATOR
@ -131,3 +145,93 @@ exports.getGlobalUserByEmail = async email => {
} }
} }
} }
exports.saveUser = async (
user,
tenantId,
hashPassword = true,
requirePassword = true
) => {
if (!tenantId) {
throw "No tenancy specified."
}
// need to set the context for this request, as specified
updateTenantId(tenantId)
// specify the tenancy incase we're making a new admin user (public)
const db = getGlobalDB(tenantId)
let { email, password, _id } = user
// make sure another user isn't using the same email
let dbUser
if (email) {
// check budibase users inside the tenant
dbUser = await exports.getGlobalUserByEmail(email)
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
throw `Email address ${email} already in use.`
}
// check budibase users in other tenants
if (env.MULTI_TENANCY) {
dbUser = await getTenantUser(email)
if (dbUser != null && dbUser.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
// check root account users in account portal
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
const account = await accounts.getAccount(email)
if (account && account.verified && account.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
} else {
dbUser = await db.get(_id)
}
// get the password, make sure one is defined
let hashedPassword
if (password) {
hashedPassword = hashPassword ? await hash(password) : password
} else if (dbUser) {
hashedPassword = dbUser.password
} else if (requirePassword) {
throw "Password must be specified."
}
_id = _id || generateGlobalUserID()
user = {
createdAt: Date.now(),
...dbUser,
...user,
_id,
password: hashedPassword,
tenantId,
}
// make sure the roles object is always present
if (!user.roles) {
user.roles = {}
}
// add the active status to a user if its not provided
if (user.status == null) {
user.status = UserStatus.ACTIVE
}
try {
const response = await db.put({
password: hashedPassword,
...user,
})
await tryAddTenant(tenantId, _id, email)
await userCache.invalidateUser(response.id)
return {
_id: response.id,
_rev: response.rev,
email,
}
} catch (err) {
if (err.status === 409) {
throw "User exists already"
} else {
throw err
}
}
}

View File

@ -1,29 +1,24 @@
const { const {
generateGlobalUserID,
getGlobalUserParams, getGlobalUserParams,
StaticDatabases, StaticDatabases,
generateNewUsageQuotaDoc, generateNewUsageQuotaDoc,
} = require("@budibase/auth/db") } = require("@budibase/auth/db")
const { hash, getGlobalUserByEmail } = require("@budibase/auth").utils const { hash, getGlobalUserByEmail, saveUser } = require("@budibase/auth").utils
const { UserStatus, EmailTemplatePurpose } = require("../../../constants") const { EmailTemplatePurpose } = require("../../../constants")
const { checkInviteCode } = require("../../../utilities/redis") const { checkInviteCode } = require("../../../utilities/redis")
const { sendEmail } = require("../../../utilities/email") const { sendEmail } = require("../../../utilities/email")
const { user: userCache } = require("@budibase/auth/cache") const { user: userCache } = require("@budibase/auth/cache")
const { invalidateSessions } = require("@budibase/auth/sessions") const { invalidateSessions } = require("@budibase/auth/sessions")
const CouchDB = require("../../../db")
const accounts = require("@budibase/auth/accounts") const accounts = require("@budibase/auth/accounts")
const { const {
getGlobalDB, getGlobalDB,
getTenantId, getTenantId,
getTenantUser,
doesTenantExist, doesTenantExist,
tryAddTenant,
updateTenantId,
} = require("@budibase/auth/tenancy") } = require("@budibase/auth/tenancy")
const { removeUserFromInfoDB } = require("@budibase/auth/deprovision") const { removeUserFromInfoDB } = require("@budibase/auth/deprovision")
const env = require("../../../environment") const env = require("../../../environment")
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
async function allUsers() { async function allUsers() {
const db = getGlobalDB() const db = getGlobalDB()
const response = await db.allDocs( const response = await db.allDocs(
@ -34,96 +29,6 @@ async function allUsers() {
return response.rows.map(row => row.doc) return response.rows.map(row => row.doc)
} }
async function saveUser(
user,
tenantId,
hashPassword = true,
requirePassword = true
) {
if (!tenantId) {
throw "No tenancy specified."
}
// need to set the context for this request, as specified
updateTenantId(tenantId)
// specify the tenancy incase we're making a new admin user (public)
const db = getGlobalDB(tenantId)
let { email, password, _id } = user
// make sure another user isn't using the same email
let dbUser
if (email) {
// check budibase users inside the tenant
dbUser = await getGlobalUserByEmail(email)
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
throw `Email address ${email} already in use.`
}
// check budibase users in other tenants
if (env.MULTI_TENANCY) {
dbUser = await getTenantUser(email)
if (dbUser != null && dbUser.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
// check root account users in account portal
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
const account = await accounts.getAccount(email)
if (account && account.verified && account.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
} else {
dbUser = await db.get(_id)
}
// get the password, make sure one is defined
let hashedPassword
if (password) {
hashedPassword = hashPassword ? await hash(password) : password
} else if (dbUser) {
hashedPassword = dbUser.password
} else if (requirePassword) {
throw "Password must be specified."
}
_id = _id || generateGlobalUserID()
user = {
createdAt: Date.now(),
...dbUser,
...user,
_id,
password: hashedPassword,
tenantId,
}
// make sure the roles object is always present
if (!user.roles) {
user.roles = {}
}
// add the active status to a user if its not provided
if (user.status == null) {
user.status = UserStatus.ACTIVE
}
try {
const response = await db.put({
password: hashedPassword,
...user,
})
await tryAddTenant(tenantId, _id, email)
await userCache.invalidateUser(response.id)
return {
_id: response.id,
_rev: response.rev,
email,
}
} catch (err) {
if (err.status === 409) {
throw "User exists already"
} else {
throw err
}
}
}
exports.save = async ctx => { exports.save = async ctx => {
try { try {
ctx.body = await saveUser(ctx.request.body, getTenantId()) ctx.body = await saveUser(ctx.request.body, getTenantId())
@ -310,16 +215,6 @@ exports.find = async ctx => {
ctx.body = user ctx.body = user
} }
// lookup, could be email or userId, either will return a doc
const getTenantUser = async identifier => {
const db = new CouchDB(PLATFORM_INFO_DB)
try {
return await db.get(identifier)
} catch (err) {
return null
}
}
exports.tenantUserLookup = async ctx => { exports.tenantUserLookup = async ctx => {
const id = ctx.params.id const id = ctx.params.id
const user = await getTenantUser(id) const user = await getTenantUser(id)