Adding last of support for per app group builder support, enriching the user on self return, as well as adding the functionality required to server middlewares.
This commit is contained in:
parent
466a2504e4
commit
656870db8b
|
@ -1 +1 @@
|
||||||
Subproject commit a054a51726fa3f17879a469a04675778185b7ed7
|
Subproject commit 39d76da2124d96b1bcec0f2352ca5ef9b0c84318
|
|
@ -2,9 +2,9 @@ import { outputProcessing } from "../../utilities/rowProcessor"
|
||||||
import { InternalTables } from "../../db/utils"
|
import { InternalTables } from "../../db/utils"
|
||||||
import { getFullUser } from "../../utilities/users"
|
import { getFullUser } from "../../utilities/users"
|
||||||
import { roles, context } from "@budibase/backend-core"
|
import { roles, context } from "@budibase/backend-core"
|
||||||
import { groups } from "@budibase/pro"
|
import { ContextUser, Row, UserCtx } from "@budibase/types"
|
||||||
import { ContextUser, User, Row, UserCtx } from "@budibase/types"
|
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
|
import { processUser } from "src/utilities/global"
|
||||||
|
|
||||||
const PUBLIC_ROLE = roles.BUILTIN_ROLE_IDS.PUBLIC
|
const PUBLIC_ROLE = roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ export async function fetchSelf(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const appId = context.getAppId()
|
const appId = context.getAppId()
|
||||||
const user: ContextUser = await getFullUser(ctx, userId)
|
let user: ContextUser = await getFullUser(ctx, userId)
|
||||||
// this shouldn't be returned by the app self
|
// this shouldn't be returned by the app self
|
||||||
delete user.roles
|
delete user.roles
|
||||||
// forward the csrf token from the session
|
// forward the csrf token from the session
|
||||||
|
@ -36,8 +36,7 @@ export async function fetchSelf(ctx: UserCtx) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
// check for group permissions
|
// check for group permissions
|
||||||
if (!user.roleId || user.roleId === PUBLIC_ROLE) {
|
if (!user.roleId || user.roleId === PUBLIC_ROLE) {
|
||||||
const groupRoleId = await groups.getGroupRoleId(user as User, appId)
|
user = await processUser(user, { appId })
|
||||||
user.roleId = groupRoleId || user.roleId
|
|
||||||
}
|
}
|
||||||
// remove the full roles structure
|
// remove the full roles structure
|
||||||
delete user.roles
|
delete user.roles
|
||||||
|
|
|
@ -12,75 +12,64 @@ import { groups } from "@budibase/pro"
|
||||||
import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
|
import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
|
||||||
import cloneDeep from "lodash/cloneDeep"
|
import cloneDeep from "lodash/cloneDeep"
|
||||||
|
|
||||||
export function updateAppRole(
|
export async function processUser(
|
||||||
user: ContextUser,
|
user: ContextUser,
|
||||||
{ appId }: { appId?: string } = {}
|
opts: { appId?: string; groups?: UserGroup[] } = {}
|
||||||
) {
|
) {
|
||||||
appId = appId || context.getAppId()
|
|
||||||
|
|
||||||
if (!user || (!user.roles && !user.userGroups)) {
|
if (!user || (!user.roles && !user.userGroups)) {
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
// if in an multi-tenancy environment make sure roles are never updated
|
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)) {
|
if (env.MULTI_TENANCY && appId && !tenancy.isUserInAppTenant(appId, user)) {
|
||||||
user = users.removePortalUserPermissions(user)
|
user = users.removePortalUserPermissions(user)
|
||||||
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
|
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
// always use the deployed app
|
let groupList: UserGroup[] = []
|
||||||
if (appId && user.roles) {
|
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)]
|
user.roleId = user.roles[dbCore.getProdAppID(appId)]
|
||||||
}
|
}
|
||||||
// if a role wasn't found then either set as admin (builder) or public (everyone else)
|
// try to get the role from the group list
|
||||||
if (!user.roleId && users.isBuilder(user, appId)) {
|
if (!user.roleId && groupList) {
|
||||||
user.roleId = roles.BUILTIN_ROLE_IDS.ADMIN
|
user.roleId = await groups.getGroupRoleId(user, appId, {
|
||||||
} else if (!user.roleId && !user?.userGroups?.length) {
|
groups: groupList,
|
||||||
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
|
|
||||||
}
|
|
||||||
|
|
||||||
delete user.roles
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkGroupRoles(
|
|
||||||
user: ContextUser,
|
|
||||||
opts: { appId?: string; groups?: UserGroup[] } = {}
|
|
||||||
) {
|
|
||||||
if (user.roleId && user.roleId !== roles.BUILTIN_ROLE_IDS.PUBLIC) {
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
if (opts.appId) {
|
|
||||||
user.roleId = await groups.getGroupRoleId(user as User, opts.appId, {
|
|
||||||
groups: opts.groups,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// final fallback, simply couldn't find a role - user must be public
|
// final fallback, simply couldn't find a role - user must be public
|
||||||
if (!user.roleId) {
|
if (!user.roleId) {
|
||||||
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
|
user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||||
}
|
}
|
||||||
|
// remove the roles as it is now set
|
||||||
|
delete user.roles
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function processUser(
|
|
||||||
user: ContextUser,
|
|
||||||
opts: { appId?: string; groups?: UserGroup[] } = {}
|
|
||||||
) {
|
|
||||||
let clonedUser = cloneDeep(user)
|
|
||||||
if (clonedUser) {
|
|
||||||
delete clonedUser.password
|
|
||||||
}
|
|
||||||
const appId = opts.appId || context.getAppId()
|
|
||||||
clonedUser = updateAppRole(clonedUser, { appId })
|
|
||||||
if (!clonedUser.roleId && clonedUser?.userGroups?.length) {
|
|
||||||
clonedUser = await checkGroupRoles(clonedUser, {
|
|
||||||
appId,
|
|
||||||
groups: opts?.groups,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return clonedUser
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCachedSelf(ctx: UserCtx, appId: string) {
|
export async function getCachedSelf(ctx: UserCtx, appId: string) {
|
||||||
// this has to be tenant aware, can't depend on the context to find it out
|
// 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
|
// running some middlewares before the tenancy causes context to break
|
||||||
|
|
|
@ -8,10 +8,9 @@ import {
|
||||||
logging,
|
logging,
|
||||||
env as coreEnv,
|
env as coreEnv,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { updateAppRole } from "./global"
|
import { Ctx, User, EmailInvite } from "@budibase/types"
|
||||||
import { BBContext, User, EmailInvite } from "@budibase/types"
|
|
||||||
|
|
||||||
export function request(ctx?: BBContext, request?: any) {
|
export function request(ctx?: Ctx, request?: any) {
|
||||||
if (!request.headers) {
|
if (!request.headers) {
|
||||||
request.headers = {}
|
request.headers = {}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +42,7 @@ export function request(ctx?: BBContext, request?: any) {
|
||||||
async function checkResponse(
|
async function checkResponse(
|
||||||
response: any,
|
response: any,
|
||||||
errorMsg: string,
|
errorMsg: string,
|
||||||
{ ctx }: { ctx?: BBContext } = {}
|
{ ctx }: { ctx?: Ctx } = {}
|
||||||
) {
|
) {
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
let error
|
let error
|
||||||
|
@ -105,21 +104,7 @@ export async function sendSmtpEmail({
|
||||||
return checkResponse(response, "send email")
|
return checkResponse(response, "send email")
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getGlobalSelf(ctx: BBContext, appId?: string) {
|
export async function removeAppFromUserRoles(ctx: Ctx, appId: string) {
|
||||||
const endpoint = `/api/global/self`
|
|
||||||
const response = await fetch(
|
|
||||||
checkSlashesInUrl(env.WORKER_URL + endpoint),
|
|
||||||
// we don't want to use API key when getting self
|
|
||||||
request(ctx, { method: "GET" })
|
|
||||||
)
|
|
||||||
let json = await checkResponse(response, "get self globally", { ctx })
|
|
||||||
if (appId) {
|
|
||||||
json = updateAppRole(json)
|
|
||||||
}
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeAppFromUserRoles(ctx: BBContext, appId: string) {
|
|
||||||
const prodAppId = dbCore.getProdAppID(appId)
|
const prodAppId = dbCore.getProdAppID(appId)
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
checkSlashesInUrl(env.WORKER_URL + `/api/global/roles/${prodAppId}`),
|
checkSlashesInUrl(env.WORKER_URL + `/api/global/roles/${prodAppId}`),
|
||||||
|
@ -130,7 +115,7 @@ export async function removeAppFromUserRoles(ctx: BBContext, appId: string) {
|
||||||
return checkResponse(response, "remove app role")
|
return checkResponse(response, "remove app role")
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function allGlobalUsers(ctx: BBContext) {
|
export async function allGlobalUsers(ctx: Ctx) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
checkSlashesInUrl(env.WORKER_URL + "/api/global/users"),
|
checkSlashesInUrl(env.WORKER_URL + "/api/global/users"),
|
||||||
// we don't want to use API key when getting self
|
// we don't want to use API key when getting self
|
||||||
|
@ -139,7 +124,7 @@ export async function allGlobalUsers(ctx: BBContext) {
|
||||||
return checkResponse(response, "get users", { ctx })
|
return checkResponse(response, "get users", { ctx })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveGlobalUser(ctx: BBContext) {
|
export async function saveGlobalUser(ctx: Ctx) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
checkSlashesInUrl(env.WORKER_URL + "/api/global/users"),
|
checkSlashesInUrl(env.WORKER_URL + "/api/global/users"),
|
||||||
// we don't want to use API key when getting self
|
// we don't want to use API key when getting self
|
||||||
|
@ -148,7 +133,7 @@ export async function saveGlobalUser(ctx: BBContext) {
|
||||||
return checkResponse(response, "save user", { ctx })
|
return checkResponse(response, "save user", { ctx })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteGlobalUser(ctx: BBContext) {
|
export async function deleteGlobalUser(ctx: Ctx) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
checkSlashesInUrl(
|
checkSlashesInUrl(
|
||||||
env.WORKER_URL + `/api/global/users/${ctx.params.userId}`
|
env.WORKER_URL + `/api/global/users/${ctx.params.userId}`
|
||||||
|
@ -159,7 +144,7 @@ export async function deleteGlobalUser(ctx: BBContext) {
|
||||||
return checkResponse(response, "delete user", { ctx })
|
return checkResponse(response, "delete user", { ctx })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readGlobalUser(ctx: BBContext): Promise<User> {
|
export async function readGlobalUser(ctx: Ctx): Promise<User> {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
checkSlashesInUrl(
|
checkSlashesInUrl(
|
||||||
env.WORKER_URL + `/api/global/users/${ctx.params.userId}`
|
env.WORKER_URL + `/api/global/users/${ctx.params.userId}`
|
||||||
|
|
|
@ -48,7 +48,7 @@ export async function generateAPIKey(ctx: any) {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
devInfo = { _id: id, userId }
|
devInfo = { _id: id, userId }
|
||||||
}
|
}
|
||||||
devInfo.apiKey = await apiKey
|
devInfo.apiKey = apiKey
|
||||||
await db.put(devInfo)
|
await db.put(devInfo)
|
||||||
ctx.body = cleanupDevInfo(devInfo)
|
ctx.body = cleanupDevInfo(devInfo)
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ export async function fetchAPIKey(ctx: any) {
|
||||||
devInfo = {
|
devInfo = {
|
||||||
_id: id,
|
_id: id,
|
||||||
userId: ctx.user._id,
|
userId: ctx.user._id,
|
||||||
apiKey: await newApiKey(),
|
apiKey: newApiKey(),
|
||||||
}
|
}
|
||||||
await db.put(devInfo)
|
await db.put(devInfo)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,11 @@ import {
|
||||||
import {
|
import {
|
||||||
accounts,
|
accounts,
|
||||||
cache,
|
cache,
|
||||||
|
ErrorCode,
|
||||||
events,
|
events,
|
||||||
migrations,
|
migrations,
|
||||||
tenancy,
|
|
||||||
platform,
|
platform,
|
||||||
ErrorCode,
|
tenancy,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { checkAnyUserExists } from "../../../utilities/users"
|
import { checkAnyUserExists } from "../../../utilities/users"
|
||||||
import { isEmailConfigured } from "../../../utilities/email"
|
import { isEmailConfigured } from "../../../utilities/email"
|
||||||
|
@ -280,7 +280,7 @@ export const onboardUsers = async (ctx: Ctx<InviteUsersRequest>) => {
|
||||||
let bulkCreateReponse = await userSdk.db.bulkCreate(users, [])
|
let bulkCreateReponse = await userSdk.db.bulkCreate(users, [])
|
||||||
|
|
||||||
// Apply temporary credentials
|
// Apply temporary credentials
|
||||||
let createWithCredentials = {
|
ctx.body = {
|
||||||
...bulkCreateReponse,
|
...bulkCreateReponse,
|
||||||
successful: bulkCreateReponse?.successful.map(user => {
|
successful: bulkCreateReponse?.successful.map(user => {
|
||||||
return {
|
return {
|
||||||
|
@ -290,8 +290,6 @@ export const onboardUsers = async (ctx: Ctx<InviteUsersRequest>) => {
|
||||||
}),
|
}),
|
||||||
created: true,
|
created: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = createWithCredentials
|
|
||||||
} else {
|
} else {
|
||||||
ctx.throw(400, "User onboarding failed")
|
ctx.throw(400, "User onboarding failed")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue