budibase/packages/server/src/utilities/global.ts

141 lines
4.1 KiB
TypeScript

import { getGlobalIDFromUserMetadataID } from "../db/utils"
import {
roles,
db as dbCore,
cache,
tenancy,
context,
users,
} from "@budibase/backend-core"
import env from "../environment"
import { groups } from "@budibase/pro"
import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
import cloneDeep from "lodash/cloneDeep"
export async function processUser(
user: ContextUser,
opts: { appId?: string; groups?: UserGroup[] } = {}
) {
if (!user || (!user.roles && !user.userGroups)) {
return user
}
user = cloneDeep(user)
delete user.password
const appId = opts.appId || context.getAppId()
if (!appId) {
throw new Error("Unable to process user without app ID")
}
// if in a multi-tenancy environment and in wrong tenant make sure roles are never updated
if (env.MULTI_TENANCY && appId && !tenancy.isUserInAppTenant(appId, user)) {
user = users.removePortalUserPermissions(user)
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
return user
}
let groupList: UserGroup[] = []
if (appId && user?.userGroups?.length) {
groupList = opts.groups
? opts.groups
: await groups.getBulk(user.userGroups)
}
// check if a group provides builder access
const builderAppIds = await groups.getGroupBuilderAppIds(user, {
appId,
groups: groupList,
})
if (builderAppIds.length && !users.isBuilder(user, appId)) {
const existingApps = user.builder?.apps || []
user.builder = {
apps: [...new Set(existingApps.concat(builderAppIds))],
}
}
// builders are always admins within the app
if (users.isBuilder(user, appId)) {
user.roleId = roles.BUILTIN_ROLE_IDS.ADMIN
}
// try to get the role from the user list
if (!user.roleId && appId && user.roles) {
user.roleId = user.roles[dbCore.getProdAppID(appId)]
}
// try to get the role from the group list
if (!user.roleId && groupList) {
user.roleId = await groups.getGroupRoleId(user, appId, {
groups: groupList,
})
}
// final fallback, simply couldn't find a role - user must be public
if (!user.roleId) {
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
}
// remove the roles as it is now set
delete user.roles
return user
}
export async function getCachedSelf(
ctx: UserCtx,
appId: string
): Promise<ContextUser> {
// this has to be tenant aware, can't depend on the context to find it out
// running some middlewares before the tenancy causes context to break
const user = await cache.user.getUser(ctx.user?._id!)
return processUser(user, { appId })
}
export async function getRawGlobalUser(userId: string): Promise<User> {
const db = tenancy.getGlobalDB()
return db.get<User>(getGlobalIDFromUserMetadataID(userId))
}
export async function getGlobalUser(userId: string): Promise<ContextUser> {
const appId = context.getAppId()
let user = await getRawGlobalUser(userId)
return processUser(user, { appId })
}
export async function getRawGlobalUsers(userIds?: string[]): Promise<User[]> {
const db = tenancy.getGlobalDB()
let globalUsers: User[]
if (userIds) {
globalUsers = await db.getMultiple<User>(userIds, { allowMissing: true })
} else {
globalUsers = (
await db.allDocs<User>(
dbCore.getGlobalUserParams(null, {
include_docs: true,
})
)
).rows.map(row => row.doc!)
}
return globalUsers
.filter(user => user != null)
.map(user => {
delete user.password
delete user.forceResetPassword
return user
})
}
export async function getGlobalUsers(
userIds?: string[]
): Promise<ContextUser[]> {
const users = await getRawGlobalUsers(userIds)
const allGroups = await groups.fetch()
return Promise.all(
users.map(user => processUser(user, { groups: allGroups }))
)
}
export async function getGlobalUsersFromMetadata(users: ContextUser[]) {
const globalUsers = await getGlobalUsers(users.map(user => user._id!))
return users.map(user => {
const globalUser = globalUsers.find(
globalUser => globalUser && user._id?.includes(globalUser._id!)
)
return {
...globalUser,
// doing user second overwrites the id and rev (always metadata)
...user,
}
})
}