Merge branch 'develop' of github.com:Budibase/budibase into design-section-feature-branch
This commit is contained in:
commit
f421ef54f3
|
@ -9,18 +9,8 @@ module.exports = () => {
|
||||||
},
|
},
|
||||||
wait: {
|
wait: {
|
||||||
type: "ports",
|
type: "ports",
|
||||||
timeout: 10000,
|
timeout: 20000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// module.exports = () => {
|
|
||||||
// return {
|
|
||||||
// dockerCompose: {
|
|
||||||
// composeFilePath: "../../hosting",
|
|
||||||
// composeFile: "docker-compose.test.yaml",
|
|
||||||
// startupTimeout: 10000,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.9.30-alpha.10",
|
"version": "2.9.30-alpha.11",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -4,6 +4,8 @@ import * as context from "../context"
|
||||||
import * as platform from "../platform"
|
import * as platform from "../platform"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import * as accounts from "../accounts"
|
import * as accounts from "../accounts"
|
||||||
|
import { UserDB } from "../users"
|
||||||
|
import { sdk } from "@budibase/shared-core"
|
||||||
|
|
||||||
const EXPIRY_SECONDS = 3600
|
const EXPIRY_SECONDS = 3600
|
||||||
|
|
||||||
|
@ -60,6 +62,18 @@ export async function getUser(
|
||||||
// make sure the tenant ID is always correct/set
|
// make sure the tenant ID is always correct/set
|
||||||
user.tenantId = tenantId
|
user.tenantId = tenantId
|
||||||
}
|
}
|
||||||
|
// if has groups, could have builder permissions granted by a group
|
||||||
|
if (user.userGroups && !sdk.users.isGlobalBuilder(user)) {
|
||||||
|
await context.doInTenant(tenantId, async () => {
|
||||||
|
const appIds = await UserDB.getGroupBuilderAppIds(user)
|
||||||
|
if (appIds.length) {
|
||||||
|
const existing = user.builder?.apps || []
|
||||||
|
user.builder = {
|
||||||
|
apps: [...new Set(existing.concat(appIds))],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,12 @@ import env from "../environment"
|
||||||
|
|
||||||
export default async (ctx: UserCtx, next: any) => {
|
export default async (ctx: UserCtx, next: any) => {
|
||||||
const appId = getAppId()
|
const appId = getAppId()
|
||||||
const builderFn = env.isWorker()
|
const builderFn =
|
||||||
? hasBuilderPermissions
|
env.isWorker() || !appId
|
||||||
: env.isApps()
|
? hasBuilderPermissions
|
||||||
? isBuilder
|
: env.isApps()
|
||||||
: undefined
|
? isBuilder
|
||||||
|
: undefined
|
||||||
if (!builderFn) {
|
if (!builderFn) {
|
||||||
throw new Error("Service name unknown - middleware inactive.")
|
throw new Error("Service name unknown - middleware inactive.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,12 @@ import env from "../environment"
|
||||||
|
|
||||||
export default async (ctx: UserCtx, next: any) => {
|
export default async (ctx: UserCtx, next: any) => {
|
||||||
const appId = getAppId()
|
const appId = getAppId()
|
||||||
const builderFn = env.isWorker()
|
const builderFn =
|
||||||
? hasBuilderPermissions
|
env.isWorker() || !appId
|
||||||
: env.isApps()
|
? hasBuilderPermissions
|
||||||
? isBuilder
|
: env.isApps()
|
||||||
: undefined
|
? isBuilder
|
||||||
|
: undefined
|
||||||
if (!builderFn) {
|
if (!builderFn) {
|
||||||
throw new Error("Service name unknown - middleware inactive.")
|
throw new Error("Service name unknown - middleware inactive.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import {
|
||||||
SaveUserOpts,
|
SaveUserOpts,
|
||||||
User,
|
User,
|
||||||
UserStatus,
|
UserStatus,
|
||||||
|
UserGroup,
|
||||||
|
ContextUser,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
getAccountHolderFromUserIds,
|
getAccountHolderFromUserIds,
|
||||||
|
@ -32,8 +34,14 @@ import { hash } from "../utils"
|
||||||
type QuotaUpdateFn = (change: number, cb?: () => Promise<any>) => Promise<any>
|
type QuotaUpdateFn = (change: number, cb?: () => Promise<any>) => Promise<any>
|
||||||
type GroupUpdateFn = (groupId: string, userIds: string[]) => Promise<any>
|
type GroupUpdateFn = (groupId: string, userIds: string[]) => Promise<any>
|
||||||
type FeatureFn = () => Promise<Boolean>
|
type FeatureFn = () => Promise<Boolean>
|
||||||
|
type GroupGetFn = (ids: string[]) => Promise<UserGroup[]>
|
||||||
|
type GroupBuildersFn = (user: User) => Promise<string[]>
|
||||||
type QuotaFns = { addUsers: QuotaUpdateFn; removeUsers: QuotaUpdateFn }
|
type QuotaFns = { addUsers: QuotaUpdateFn; removeUsers: QuotaUpdateFn }
|
||||||
type GroupFns = { addUsers: GroupUpdateFn }
|
type GroupFns = {
|
||||||
|
addUsers: GroupUpdateFn
|
||||||
|
getBulk: GroupGetFn
|
||||||
|
getGroupBuilderAppIds: GroupBuildersFn
|
||||||
|
}
|
||||||
type FeatureFns = { isSSOEnforced: FeatureFn; isAppBuildersEnabled: FeatureFn }
|
type FeatureFns = { isSSOEnforced: FeatureFn; isAppBuildersEnabled: FeatureFn }
|
||||||
|
|
||||||
const bulkDeleteProcessing = async (dbUser: User) => {
|
const bulkDeleteProcessing = async (dbUser: User) => {
|
||||||
|
@ -465,4 +473,12 @@ export class UserDB {
|
||||||
await cache.user.invalidateUser(userId)
|
await cache.user.invalidateUser(userId)
|
||||||
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getGroups(groupIds: string[]) {
|
||||||
|
return await this.groups.getBulk(groupIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getGroupBuilderAppIds(user: User) {
|
||||||
|
return await this.groups.getGroupBuilderAppIds(user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 "../../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
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
import { generator } from "@budibase/backend-core/tests"
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
import { utils } from "@budibase/backend-core"
|
import { utils } from "@budibase/backend-core"
|
||||||
import { GenericContainer } from "testcontainers"
|
import { GenericContainer, Wait, StartedTestContainer } from "testcontainers"
|
||||||
|
|
||||||
const config = setup.getConfig()!
|
const config = setup.getConfig()!
|
||||||
|
|
||||||
|
@ -37,22 +37,36 @@ describe("postgres integrations", () => {
|
||||||
|
|
||||||
let host: string
|
let host: string
|
||||||
let port: number
|
let port: number
|
||||||
|
const containers: StartedTestContainer[] = []
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const container = await new GenericContainer("postgres")
|
const containerPostgres = await new GenericContainer("postgres")
|
||||||
.withExposedPorts(5432)
|
.withExposedPorts(5432)
|
||||||
.withEnv("POSTGRES_PASSWORD", "password")
|
.withEnv("POSTGRES_PASSWORD", "password")
|
||||||
|
.withWaitStrategy(
|
||||||
|
Wait.forLogMessage(
|
||||||
|
"PostgreSQL init process complete; ready for start up."
|
||||||
|
)
|
||||||
|
)
|
||||||
.start()
|
.start()
|
||||||
|
|
||||||
host = container.getContainerIpAddress()
|
host = containerPostgres.getContainerIpAddress()
|
||||||
port = container.getMappedPort(5432)
|
port = containerPostgres.getMappedPort(5432)
|
||||||
|
|
||||||
await config.init()
|
await config.init()
|
||||||
const apiKey = await config.generateApiKey()
|
const apiKey = await config.generateApiKey()
|
||||||
|
|
||||||
|
containers.push(containerPostgres)
|
||||||
|
|
||||||
makeRequest = generateMakeRequest(apiKey, true)
|
makeRequest = generateMakeRequest(apiKey, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
for (let container of containers) {
|
||||||
|
await container.stop()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function pgDatasourceConfig() {
|
function pgDatasourceConfig() {
|
||||||
return {
|
return {
|
||||||
datasource: {
|
datasource: {
|
||||||
|
|
|
@ -12,75 +12,65 @@ 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}`
|
||||||
|
|
|
@ -7,6 +7,10 @@ export interface UserGroup extends Document {
|
||||||
color: string
|
color: string
|
||||||
users?: GroupUser[]
|
users?: GroupUser[]
|
||||||
roles?: UserGroupRoles
|
roles?: UserGroupRoles
|
||||||
|
// same structure as users
|
||||||
|
builder?: {
|
||||||
|
apps: string[]
|
||||||
|
}
|
||||||
createdAt?: number
|
createdAt?: number
|
||||||
scimInfo?: {
|
scimInfo?: {
|
||||||
externalId: string
|
externalId: string
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,9 @@ describe("/api/global/users/:userId/app/builder", () => {
|
||||||
MOCK_APP_ID,
|
MOCK_APP_ID,
|
||||||
400
|
400
|
||||||
)
|
)
|
||||||
expect(resp.body.message).toContain("Feature not enabled")
|
expect(resp.body.message).toContain(
|
||||||
|
"appBuilders are not currently enabled"
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ import {
|
||||||
import { TestConfiguration } from "../../../../tests"
|
import { TestConfiguration } from "../../../../tests"
|
||||||
import { events } from "@budibase/backend-core"
|
import { events } from "@budibase/backend-core"
|
||||||
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
mocks.licenses.useScimIntegration()
|
mocks.licenses.useScimIntegration()
|
||||||
|
|
||||||
describe("scim", () => {
|
describe("scim", () => {
|
||||||
|
|
Loading…
Reference in New Issue