@budibase/worker 2.33.2 → 2.33.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/api/controllers/global/configs.ts +34 -22
- package/src/api/controllers/global/tests/configs.spec.ts +0 -4
- package/src/api/controllers/global/users.ts +28 -6
- package/src/api/controllers/system/environment.ts +5 -2
- package/src/api/index.ts +1 -9
- package/src/api/routes/global/tests/roles.spec.ts +56 -1
- package/src/api/routes/global/tests/users.spec.ts +10 -66
- package/src/api/routes/global/users.ts +1 -0
- package/src/api/routes/index.ts +0 -2
- package/src/api/routes/validation/users.ts +8 -1
- package/src/tests/api/tenants.ts +0 -9
- package/src/tests/api/users.ts +8 -2
- package/src/api/controllers/global/tenant.ts +0 -14
- package/src/api/routes/global/tenant.ts +0 -11
- package/src/api/routes/global/tests/tenant.spec.ts +0 -48
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/worker",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "2.33.
|
|
4
|
+
"version": "2.33.4",
|
|
5
5
|
"description": "Budibase background service",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"author": "Budibase",
|
|
38
38
|
"license": "GPL-3.0",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@budibase/backend-core": "2.33.
|
|
41
|
-
"@budibase/pro": "2.33.
|
|
42
|
-
"@budibase/string-templates": "2.33.
|
|
43
|
-
"@budibase/types": "2.33.
|
|
40
|
+
"@budibase/backend-core": "2.33.4",
|
|
41
|
+
"@budibase/pro": "2.33.4",
|
|
42
|
+
"@budibase/string-templates": "2.33.4",
|
|
43
|
+
"@budibase/types": "2.33.4",
|
|
44
44
|
"@koa/router": "8.0.8",
|
|
45
45
|
"@techpass/passport-openidconnect": "0.3.3",
|
|
46
46
|
"@types/global-agent": "2.1.1",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
|
-
"gitHead": "
|
|
110
|
+
"gitHead": "3fdac98d5137c43e5c8593f24a546338bc0ec36f"
|
|
111
111
|
}
|
|
@@ -44,9 +44,7 @@ const getEventFns = async (config: Config, existing?: Config) => {
|
|
|
44
44
|
fns.push(events.email.SMTPCreated)
|
|
45
45
|
} else if (isAIConfig(config)) {
|
|
46
46
|
fns.push(() => events.ai.AIConfigCreated)
|
|
47
|
-
fns.push(() =>
|
|
48
|
-
pro.quotas.updateCustomAIConfigCount(Object.keys(config.config).length)
|
|
49
|
-
)
|
|
47
|
+
fns.push(() => pro.quotas.addCustomAIConfig())
|
|
50
48
|
} else if (isGoogleConfig(config)) {
|
|
51
49
|
fns.push(() => events.auth.SSOCreated(ConfigType.GOOGLE))
|
|
52
50
|
if (config.config.activated) {
|
|
@@ -85,9 +83,6 @@ const getEventFns = async (config: Config, existing?: Config) => {
|
|
|
85
83
|
fns.push(events.email.SMTPUpdated)
|
|
86
84
|
} else if (isAIConfig(config)) {
|
|
87
85
|
fns.push(() => events.ai.AIConfigUpdated)
|
|
88
|
-
fns.push(() =>
|
|
89
|
-
pro.quotas.updateCustomAIConfigCount(Object.keys(config.config).length)
|
|
90
|
-
)
|
|
91
86
|
} else if (isGoogleConfig(config)) {
|
|
92
87
|
fns.push(() => events.auth.SSOUpdated(ConfigType.GOOGLE))
|
|
93
88
|
if (!existing.config.activated && config.config.activated) {
|
|
@@ -253,7 +248,7 @@ export async function save(ctx: UserCtx<Config>) {
|
|
|
253
248
|
if (existingConfig) {
|
|
254
249
|
await verifyAIConfig(config, existingConfig)
|
|
255
250
|
}
|
|
256
|
-
await pro.quotas.
|
|
251
|
+
await pro.quotas.addCustomAIConfig()
|
|
257
252
|
break
|
|
258
253
|
}
|
|
259
254
|
} catch (err: any) {
|
|
@@ -342,29 +337,43 @@ export async function find(ctx: UserCtx) {
|
|
|
342
337
|
let scopedConfig = await configs.getConfig(type)
|
|
343
338
|
|
|
344
339
|
if (scopedConfig) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (type === ConfigType.AI) {
|
|
350
|
-
await pro.sdk.ai.enrichAIConfig(scopedConfig)
|
|
351
|
-
// Strip out the API Keys from the response so they don't show in the UI
|
|
352
|
-
for (const key in scopedConfig.config) {
|
|
353
|
-
if (scopedConfig.config[key].apiKey) {
|
|
354
|
-
scopedConfig.config[key].apiKey = PASSWORD_REPLACEMENT
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
ctx.body = scopedConfig
|
|
340
|
+
await handleConfigType(type, scopedConfig)
|
|
341
|
+
} else if (type === ConfigType.AI) {
|
|
342
|
+
scopedConfig = { config: {} } as AIConfig
|
|
343
|
+
await handleAIConfig(scopedConfig)
|
|
359
344
|
} else {
|
|
360
|
-
//
|
|
345
|
+
// If no config found and not AI type, just return an empty body
|
|
361
346
|
ctx.body = {}
|
|
347
|
+
return
|
|
362
348
|
}
|
|
349
|
+
|
|
350
|
+
ctx.body = scopedConfig
|
|
363
351
|
} catch (err: any) {
|
|
364
352
|
ctx.throw(err?.status || 400, err)
|
|
365
353
|
}
|
|
366
354
|
}
|
|
367
355
|
|
|
356
|
+
async function handleConfigType(type: ConfigType, config: Config) {
|
|
357
|
+
if (type === ConfigType.OIDC_LOGOS) {
|
|
358
|
+
enrichOIDCLogos(config)
|
|
359
|
+
} else if (type === ConfigType.AI) {
|
|
360
|
+
await handleAIConfig(config)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function handleAIConfig(config: AIConfig) {
|
|
365
|
+
await pro.sdk.ai.enrichAIConfig(config)
|
|
366
|
+
stripApiKeys(config)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function stripApiKeys(config: AIConfig) {
|
|
370
|
+
for (const key in config?.config) {
|
|
371
|
+
if (config.config[key].apiKey) {
|
|
372
|
+
config.config[key].apiKey = PASSWORD_REPLACEMENT
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
368
377
|
export async function publicOidc(ctx: Ctx<void, GetPublicOIDCConfigResponse>) {
|
|
369
378
|
try {
|
|
370
379
|
// Find the config with the most granular scope based on context
|
|
@@ -508,6 +517,9 @@ export async function destroy(ctx: UserCtx) {
|
|
|
508
517
|
try {
|
|
509
518
|
await db.remove(id, rev)
|
|
510
519
|
await cache.destroy(cache.CacheKey.CHECKLIST)
|
|
520
|
+
if (id === configs.generateConfigID(ConfigType.AI)) {
|
|
521
|
+
await pro.quotas.removeCustomAIConfig()
|
|
522
|
+
}
|
|
511
523
|
ctx.body = { message: "Config deleted successfully" }
|
|
512
524
|
} catch (err: any) {
|
|
513
525
|
ctx.throw(err.status, err)
|
|
@@ -13,10 +13,6 @@ describe("Global configs controller", () => {
|
|
|
13
13
|
await config.afterAll()
|
|
14
14
|
})
|
|
15
15
|
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
jest.resetAllMocks()
|
|
18
|
-
})
|
|
19
|
-
|
|
20
16
|
it("Should strip secrets when pulling AI config", async () => {
|
|
21
17
|
const data = structures.configs.ai()
|
|
22
18
|
await config.api.configs.saveConfig(data)
|
|
@@ -23,9 +23,11 @@ import {
|
|
|
23
23
|
SearchUsersRequest,
|
|
24
24
|
User,
|
|
25
25
|
UserCtx,
|
|
26
|
+
UserIdentifier,
|
|
26
27
|
} from "@budibase/types"
|
|
27
28
|
import {
|
|
28
29
|
accounts,
|
|
30
|
+
users,
|
|
29
31
|
cache,
|
|
30
32
|
ErrorCode,
|
|
31
33
|
events,
|
|
@@ -55,8 +57,8 @@ export const save = async (ctx: UserCtx<User, SaveUserResponse>) => {
|
|
|
55
57
|
const requestUser = ctx.request.body
|
|
56
58
|
|
|
57
59
|
// Do not allow the account holder role to be changed
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
+
const accountMetadata = await users.getExistingAccounts([requestUser.email])
|
|
61
|
+
if (accountMetadata?.length > 0) {
|
|
60
62
|
if (
|
|
61
63
|
requestUser.admin?.global !== true ||
|
|
62
64
|
requestUser.builder?.global !== true
|
|
@@ -103,11 +105,14 @@ export const addSsoSupport = async (ctx: Ctx<AddSSoUserRequest>) => {
|
|
|
103
105
|
}
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
const bulkDelete = async (
|
|
107
|
-
|
|
108
|
+
const bulkDelete = async (
|
|
109
|
+
users: Array<UserIdentifier>,
|
|
110
|
+
currentUserId: string
|
|
111
|
+
) => {
|
|
112
|
+
if (users.find(u => u.userId === currentUserId)) {
|
|
108
113
|
throw new Error("Unable to delete self.")
|
|
109
114
|
}
|
|
110
|
-
return await userSdk.db.bulkDelete(
|
|
115
|
+
return await userSdk.db.bulkDelete(users)
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
const bulkCreate = async (users: User[], groupIds: string[]) => {
|
|
@@ -130,7 +135,7 @@ export const bulkUpdate = async (
|
|
|
130
135
|
created = await bulkCreate(input.create.users, input.create.groups)
|
|
131
136
|
}
|
|
132
137
|
if (input.delete) {
|
|
133
|
-
deleted = await bulkDelete(input.delete.
|
|
138
|
+
deleted = await bulkDelete(input.delete.users, currentUserId)
|
|
134
139
|
}
|
|
135
140
|
} catch (err: any) {
|
|
136
141
|
ctx.throw(err.status || 400, err?.message || err)
|
|
@@ -302,6 +307,23 @@ export const tenantUserLookup = async (ctx: any) => {
|
|
|
302
307
|
}
|
|
303
308
|
}
|
|
304
309
|
|
|
310
|
+
/**
|
|
311
|
+
* This will be paginated to a default of the first 50 users,
|
|
312
|
+
* So the account holder may not be found until further pagination has occurred
|
|
313
|
+
*/
|
|
314
|
+
export const accountHolderLookup = async (ctx: Ctx) => {
|
|
315
|
+
const users = await userSdk.core.getAllUsers()
|
|
316
|
+
const response = await userSdk.core.getExistingAccounts(
|
|
317
|
+
users.map(u => u.email)
|
|
318
|
+
)
|
|
319
|
+
const holder = response[0]
|
|
320
|
+
if (!holder) {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
holder._id = users.find(u => u.email === holder.email)?._id
|
|
324
|
+
ctx.body = holder
|
|
325
|
+
}
|
|
326
|
+
|
|
305
327
|
/*
|
|
306
328
|
Encapsulate the app user onboarding flows here.
|
|
307
329
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ctx, MaintenanceType } from "@budibase/types"
|
|
1
|
+
import { Ctx, MaintenanceType, FeatureFlag } from "@budibase/types"
|
|
2
2
|
import env from "../../../environment"
|
|
3
3
|
import { env as coreEnv, db as dbCore, features } from "@budibase/backend-core"
|
|
4
4
|
import nodeFetch from "node-fetch"
|
|
@@ -29,7 +29,10 @@ async function isSqsAvailable() {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async function isSqsMissing() {
|
|
32
|
-
return (
|
|
32
|
+
return (
|
|
33
|
+
(await features.flags.isEnabled(FeatureFlag.SQS)) &&
|
|
34
|
+
!(await isSqsAvailable())
|
|
35
|
+
)
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export const fetch = async (ctx: Ctx) => {
|
package/src/api/index.ts
CHANGED
|
@@ -71,10 +71,6 @@ const PUBLIC_ENDPOINTS = [
|
|
|
71
71
|
route: "/api/global/users/invite",
|
|
72
72
|
method: "GET",
|
|
73
73
|
},
|
|
74
|
-
{
|
|
75
|
-
route: "/api/global/tenant",
|
|
76
|
-
method: "POST",
|
|
77
|
-
},
|
|
78
74
|
]
|
|
79
75
|
|
|
80
76
|
const NO_TENANCY_ENDPOINTS = [
|
|
@@ -121,11 +117,7 @@ const NO_TENANCY_ENDPOINTS = [
|
|
|
121
117
|
method: "GET",
|
|
122
118
|
},
|
|
123
119
|
{
|
|
124
|
-
route: "/api/global/
|
|
125
|
-
method: "POST",
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
route: "/api/global/tenant/:id",
|
|
120
|
+
route: "/api/global/users/accountholder",
|
|
129
121
|
method: "GET",
|
|
130
122
|
},
|
|
131
123
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { structures, TestConfiguration } from "../../../../tests"
|
|
2
2
|
import { context, db, permissions, roles } from "@budibase/backend-core"
|
|
3
|
-
import { Database } from "@budibase/types"
|
|
3
|
+
import { App, Database } from "@budibase/types"
|
|
4
4
|
|
|
5
5
|
jest.mock("@budibase/backend-core", () => {
|
|
6
6
|
const core = jest.requireActual("@budibase/backend-core")
|
|
@@ -30,6 +30,14 @@ async function addAppMetadata() {
|
|
|
30
30
|
})
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
async function updateAppMetadata(update: Partial<Omit<App, "_id" | "_rev">>) {
|
|
34
|
+
const app = await appDb.get("app_metadata")
|
|
35
|
+
await appDb.put({
|
|
36
|
+
...app,
|
|
37
|
+
...update,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
describe("/api/global/roles", () => {
|
|
34
42
|
const config = new TestConfiguration()
|
|
35
43
|
|
|
@@ -69,6 +77,53 @@ describe("/api/global/roles", () => {
|
|
|
69
77
|
expect(res.body[appId].roles.length).toEqual(5)
|
|
70
78
|
expect(res.body[appId].roles.map((r: any) => r._id)).toContain(ROLE_NAME)
|
|
71
79
|
})
|
|
80
|
+
|
|
81
|
+
it.each(["3.0.0", "3.0.1", "3.1.0", "3.0.0+2146.b125a7c"])(
|
|
82
|
+
"exclude POWER roles after v3 (%s)",
|
|
83
|
+
async creationVersion => {
|
|
84
|
+
await updateAppMetadata({ creationVersion })
|
|
85
|
+
const res = await config.api.roles.get()
|
|
86
|
+
expect(res.body).toBeDefined()
|
|
87
|
+
expect(res.body[appId].roles.map((r: any) => r._id)).toEqual([
|
|
88
|
+
ROLE_NAME,
|
|
89
|
+
roles.BUILTIN_ROLE_IDS.ADMIN,
|
|
90
|
+
roles.BUILTIN_ROLE_IDS.BASIC,
|
|
91
|
+
roles.BUILTIN_ROLE_IDS.PUBLIC,
|
|
92
|
+
])
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
it.each(["2.9.0", "1.0.0", "0.0.0", "2.32.17+2146.b125a7c"])(
|
|
97
|
+
"include POWER roles before v3 (%s)",
|
|
98
|
+
async creationVersion => {
|
|
99
|
+
await updateAppMetadata({ creationVersion })
|
|
100
|
+
const res = await config.api.roles.get()
|
|
101
|
+
expect(res.body).toBeDefined()
|
|
102
|
+
expect(res.body[appId].roles.map((r: any) => r._id)).toEqual([
|
|
103
|
+
ROLE_NAME,
|
|
104
|
+
roles.BUILTIN_ROLE_IDS.ADMIN,
|
|
105
|
+
roles.BUILTIN_ROLE_IDS.POWER,
|
|
106
|
+
roles.BUILTIN_ROLE_IDS.BASIC,
|
|
107
|
+
roles.BUILTIN_ROLE_IDS.PUBLIC,
|
|
108
|
+
])
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
it.each(["invalid", ""])(
|
|
113
|
+
"include POWER roles when the version is corrupted (%s)",
|
|
114
|
+
async creationVersion => {
|
|
115
|
+
await updateAppMetadata({ creationVersion })
|
|
116
|
+
const res = await config.api.roles.get()
|
|
117
|
+
|
|
118
|
+
expect(res.body[appId].roles.map((r: any) => r._id)).toEqual([
|
|
119
|
+
ROLE_NAME,
|
|
120
|
+
roles.BUILTIN_ROLE_IDS.ADMIN,
|
|
121
|
+
roles.BUILTIN_ROLE_IDS.POWER,
|
|
122
|
+
roles.BUILTIN_ROLE_IDS.BASIC,
|
|
123
|
+
roles.BUILTIN_ROLE_IDS.PUBLIC,
|
|
124
|
+
])
|
|
125
|
+
}
|
|
126
|
+
)
|
|
72
127
|
})
|
|
73
128
|
|
|
74
129
|
describe("GET api/global/roles/:appId", () => {
|
|
@@ -412,28 +412,6 @@ describe("/api/global/users", () => {
|
|
|
412
412
|
expect(events.user.permissionBuilderRemoved).toHaveBeenCalledTimes(1)
|
|
413
413
|
})
|
|
414
414
|
|
|
415
|
-
it("should not be able to update an account holder user to a basic user", async () => {
|
|
416
|
-
const accountHolderUser = await config.createUser(
|
|
417
|
-
structures.users.adminUser()
|
|
418
|
-
)
|
|
419
|
-
jest.clearAllMocks()
|
|
420
|
-
tenancy.getTenantInfo = jest.fn().mockImplementation(() => ({
|
|
421
|
-
owner: {
|
|
422
|
-
email: accountHolderUser.email,
|
|
423
|
-
},
|
|
424
|
-
}))
|
|
425
|
-
|
|
426
|
-
accountHolderUser.admin!.global = false
|
|
427
|
-
accountHolderUser.builder!.global = false
|
|
428
|
-
|
|
429
|
-
await config.api.users.saveUser(accountHolderUser, 400)
|
|
430
|
-
|
|
431
|
-
expect(events.user.created).not.toHaveBeenCalled()
|
|
432
|
-
expect(events.user.updated).not.toHaveBeenCalled()
|
|
433
|
-
expect(events.user.permissionAdminRemoved).not.toHaveBeenCalled()
|
|
434
|
-
expect(events.user.permissionBuilderRemoved).not.toHaveBeenCalled()
|
|
435
|
-
})
|
|
436
|
-
|
|
437
415
|
it("should be able to update an builder user to a basic user", async () => {
|
|
438
416
|
const user = await config.createUser(structures.users.builderUser())
|
|
439
417
|
jest.clearAllMocks()
|
|
@@ -592,55 +570,21 @@ describe("/api/global/users", () => {
|
|
|
592
570
|
|
|
593
571
|
describe("POST /api/global/users/bulk (delete)", () => {
|
|
594
572
|
it("should not be able to bulk delete current user", async () => {
|
|
595
|
-
const user =
|
|
573
|
+
const user = config.user!
|
|
596
574
|
|
|
597
|
-
const response = await config.api.users.bulkDeleteUsers(
|
|
575
|
+
const response = await config.api.users.bulkDeleteUsers(
|
|
576
|
+
[
|
|
577
|
+
{
|
|
578
|
+
userId: user._id!,
|
|
579
|
+
email: "test@example.com",
|
|
580
|
+
},
|
|
581
|
+
],
|
|
582
|
+
400
|
|
583
|
+
)
|
|
598
584
|
|
|
599
585
|
expect(response.message).toBe("Unable to delete self.")
|
|
600
586
|
expect(events.user.deleted).not.toHaveBeenCalled()
|
|
601
587
|
})
|
|
602
|
-
|
|
603
|
-
it("should not be able to bulk delete account owner", async () => {
|
|
604
|
-
const user = await config.createUser()
|
|
605
|
-
const account = structures.accounts.cloudAccount()
|
|
606
|
-
account.budibaseUserId = user._id!
|
|
607
|
-
accounts.getAccountByTenantId.mockReturnValue(Promise.resolve(account))
|
|
608
|
-
|
|
609
|
-
const response = await config.api.users.bulkDeleteUsers([user._id!])
|
|
610
|
-
|
|
611
|
-
expect(response.deleted?.successful.length).toBe(0)
|
|
612
|
-
expect(response.deleted?.unsuccessful.length).toBe(1)
|
|
613
|
-
expect(response.deleted?.unsuccessful[0].reason).toBe(
|
|
614
|
-
"Account holder cannot be deleted"
|
|
615
|
-
)
|
|
616
|
-
expect(response.deleted?.unsuccessful[0]._id).toBe(user._id)
|
|
617
|
-
expect(events.user.deleted).not.toHaveBeenCalled()
|
|
618
|
-
})
|
|
619
|
-
|
|
620
|
-
it("should be able to bulk delete users", async () => {
|
|
621
|
-
const account = structures.accounts.cloudAccount()
|
|
622
|
-
accounts.getAccountByTenantId.mockReturnValue(Promise.resolve(account))
|
|
623
|
-
|
|
624
|
-
const builder = structures.users.builderUser()
|
|
625
|
-
const admin = structures.users.adminUser()
|
|
626
|
-
const user = structures.users.user()
|
|
627
|
-
const createdUsers = await config.api.users.bulkCreateUsers([
|
|
628
|
-
builder,
|
|
629
|
-
admin,
|
|
630
|
-
user,
|
|
631
|
-
])
|
|
632
|
-
|
|
633
|
-
const toDelete = createdUsers.created?.successful.map(
|
|
634
|
-
u => u._id!
|
|
635
|
-
) as string[]
|
|
636
|
-
const response = await config.api.users.bulkDeleteUsers(toDelete)
|
|
637
|
-
|
|
638
|
-
expect(response.deleted?.successful.length).toBe(3)
|
|
639
|
-
expect(response.deleted?.unsuccessful.length).toBe(0)
|
|
640
|
-
expect(events.user.deleted).toHaveBeenCalledTimes(3)
|
|
641
|
-
expect(events.user.permissionAdminRemoved).toHaveBeenCalledTimes(1)
|
|
642
|
-
expect(events.user.permissionBuilderRemoved).toHaveBeenCalledTimes(2)
|
|
643
|
-
})
|
|
644
588
|
})
|
|
645
589
|
|
|
646
590
|
describe("POST /api/global/users/search", () => {
|
|
@@ -136,6 +136,7 @@ router
|
|
|
136
136
|
buildAdminInitValidation(),
|
|
137
137
|
controller.adminUser
|
|
138
138
|
)
|
|
139
|
+
.get("/api/global/users/accountholder", controller.accountHolderLookup)
|
|
139
140
|
.get("/api/global/users/tenant/:id", controller.tenantUserLookup)
|
|
140
141
|
// global endpoint but needs to come at end (blocks other endpoints otherwise)
|
|
141
142
|
.get("/api/global/users/:id", auth.builderOrAdmin, controller.find)
|
package/src/api/routes/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Router from "@koa/router"
|
|
2
2
|
import { api as pro } from "@budibase/pro"
|
|
3
3
|
import userRoutes from "./global/users"
|
|
4
|
-
import tenantRoutes from "./global/tenant"
|
|
5
4
|
import configRoutes from "./global/configs"
|
|
6
5
|
import workspaceRoutes from "./global/workspaces"
|
|
7
6
|
import templateRoutes from "./global/templates"
|
|
@@ -41,7 +40,6 @@ export const routes: Router[] = [
|
|
|
41
40
|
accountRoutes,
|
|
42
41
|
restoreRoutes,
|
|
43
42
|
eventRoutes,
|
|
44
|
-
tenantRoutes,
|
|
45
43
|
pro.scim,
|
|
46
44
|
]
|
|
47
45
|
|
|
@@ -66,7 +66,14 @@ export const buildUserBulkUserValidation = (isSelf = false) => {
|
|
|
66
66
|
users: Joi.array().items(Joi.object(schema).required().unknown(true)),
|
|
67
67
|
}),
|
|
68
68
|
delete: Joi.object({
|
|
69
|
-
|
|
69
|
+
users: Joi.array().items(
|
|
70
|
+
Joi.object({
|
|
71
|
+
email: Joi.string(),
|
|
72
|
+
userId: Joi.string(),
|
|
73
|
+
})
|
|
74
|
+
.required()
|
|
75
|
+
.unknown(true)
|
|
76
|
+
),
|
|
70
77
|
}),
|
|
71
78
|
}
|
|
72
79
|
|
package/src/tests/api/tenants.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { TenantInfo } from "@budibase/types"
|
|
2
1
|
import TestConfiguration from "../TestConfiguration"
|
|
3
2
|
import { TestAPI, TestAPIOpts } from "./base"
|
|
4
3
|
|
|
@@ -15,12 +14,4 @@ export class TenantAPI extends TestAPI {
|
|
|
15
14
|
.set(opts?.headers)
|
|
16
15
|
.expect(opts?.status ? opts.status : 204)
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
saveTenantInfo = (tenantInfo: TenantInfo) => {
|
|
20
|
-
return this.request
|
|
21
|
-
.post("/api/global/tenant")
|
|
22
|
-
.set(this.config.internalAPIHeaders())
|
|
23
|
-
.send(tenantInfo)
|
|
24
|
-
.expect(200)
|
|
25
|
-
}
|
|
26
17
|
}
|
package/src/tests/api/users.ts
CHANGED
|
@@ -81,8 +81,14 @@ export class UserAPI extends TestAPI {
|
|
|
81
81
|
return res.body as BulkUserResponse
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
bulkDeleteUsers = async (
|
|
85
|
-
|
|
84
|
+
bulkDeleteUsers = async (
|
|
85
|
+
users: Array<{
|
|
86
|
+
userId: string
|
|
87
|
+
email: string
|
|
88
|
+
}>,
|
|
89
|
+
status?: number
|
|
90
|
+
) => {
|
|
91
|
+
const body: BulkUserRequest = { delete: { users } }
|
|
86
92
|
const res = await this.request
|
|
87
93
|
.post(`/api/global/users/bulk`)
|
|
88
94
|
.send(body)
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { tenancy } from "@budibase/backend-core"
|
|
2
|
-
import { TenantInfo, Ctx } from "@budibase/types"
|
|
3
|
-
|
|
4
|
-
export const save = async (ctx: Ctx<TenantInfo>) => {
|
|
5
|
-
const response = await tenancy.saveTenantInfo(ctx.request.body)
|
|
6
|
-
ctx.body = {
|
|
7
|
-
_id: response.id,
|
|
8
|
-
_rev: response.rev,
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const get = async (ctx: Ctx) => {
|
|
13
|
-
ctx.body = await tenancy.getTenantInfo(ctx.params.id)
|
|
14
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import Router from "@koa/router"
|
|
2
|
-
import * as controller from "../../controllers/global/tenant"
|
|
3
|
-
import cloudRestricted from "../../../middleware/cloudRestricted"
|
|
4
|
-
|
|
5
|
-
const router: Router = new Router()
|
|
6
|
-
|
|
7
|
-
router
|
|
8
|
-
.post("/api/global/tenant", cloudRestricted, controller.save)
|
|
9
|
-
.get("/api/global/tenant/:id", controller.get)
|
|
10
|
-
|
|
11
|
-
export default router
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Hosting, TenantInfo } from "@budibase/types"
|
|
2
|
-
import { TestConfiguration } from "../../../../tests"
|
|
3
|
-
import { tenancy as _tenancy } from "@budibase/backend-core"
|
|
4
|
-
|
|
5
|
-
const tenancy = jest.mocked(_tenancy)
|
|
6
|
-
|
|
7
|
-
describe("/api/global/tenant", () => {
|
|
8
|
-
const config = new TestConfiguration()
|
|
9
|
-
|
|
10
|
-
beforeAll(async () => {
|
|
11
|
-
await config.beforeAll()
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
afterAll(async () => {
|
|
15
|
-
await config.afterAll()
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
jest.clearAllMocks()
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
describe("POST /api/global/tenant", () => {
|
|
23
|
-
it("should save the tenantInfo", async () => {
|
|
24
|
-
tenancy.saveTenantInfo = jest.fn().mockImplementation(async () => ({
|
|
25
|
-
id: "DOC_ID",
|
|
26
|
-
ok: true,
|
|
27
|
-
rev: "DOC_REV",
|
|
28
|
-
}))
|
|
29
|
-
const tenantInfo: TenantInfo = {
|
|
30
|
-
owner: {
|
|
31
|
-
email: "test@example.com",
|
|
32
|
-
password: "PASSWORD123!",
|
|
33
|
-
ssoId: "SSO_ID",
|
|
34
|
-
givenName: "Jane",
|
|
35
|
-
familyName: "Doe",
|
|
36
|
-
budibaseUserId: "USER_ID",
|
|
37
|
-
},
|
|
38
|
-
tenantId: "tenant123",
|
|
39
|
-
hosting: Hosting.CLOUD,
|
|
40
|
-
}
|
|
41
|
-
const response = await config.api.tenants.saveTenantInfo(tenantInfo)
|
|
42
|
-
|
|
43
|
-
expect(_tenancy.saveTenantInfo).toHaveBeenCalledTimes(1)
|
|
44
|
-
expect(_tenancy.saveTenantInfo).toHaveBeenCalledWith(tenantInfo)
|
|
45
|
-
expect(response.text).toEqual('{"_id":"DOC_ID","_rev":"DOC_REV"}')
|
|
46
|
-
})
|
|
47
|
-
})
|
|
48
|
-
})
|