@budibase/worker 3.31.1 → 3.31.3
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/jest.config.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Config } from "jest"
|
|
2
|
-
import * as fs from "fs"
|
|
3
2
|
|
|
4
3
|
const config: Config = {
|
|
5
4
|
globalSetup: "./../../globalSetup.ts",
|
|
@@ -16,13 +15,9 @@ const config: Config = {
|
|
|
16
15
|
"@budibase/types": "<rootDir>/../types/src",
|
|
17
16
|
"@budibase/shared-core": ["<rootDir>/../shared-core/src"],
|
|
18
17
|
"@budibase/string-templates": ["<rootDir>/../string-templates/src"],
|
|
18
|
+
"@budibase/pro/(.*)": "<rootDir>/../pro/$1",
|
|
19
|
+
"@budibase/pro": "<rootDir>/../pro/src",
|
|
19
20
|
},
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
// add pro sources if they exist
|
|
23
|
-
if (fs.existsSync("../pro/src")) {
|
|
24
|
-
config.moduleNameMapper!["@budibase/pro/(.*)"] = "<rootDir>/../pro/$1"
|
|
25
|
-
config.moduleNameMapper!["@budibase/pro"] = "<rootDir>/../pro/src"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
23
|
export default config
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/worker",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "3.31.
|
|
4
|
+
"version": "3.31.3",
|
|
5
5
|
"description": "Budibase background service",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
"@govtechsg/passport-openidconnect": "1.0.3",
|
|
38
38
|
"@koa/router": "15.3.0",
|
|
39
39
|
"@types/global-agent": "2.1.1",
|
|
40
|
-
"aws-sdk": "2.1692.0",
|
|
41
40
|
"bcrypt": "6.0.0",
|
|
42
41
|
"bull": "4.10.1",
|
|
43
42
|
"dd-trace": "5.63.0",
|
|
@@ -48,28 +47,19 @@
|
|
|
48
47
|
"ical-generator": "4.1.0",
|
|
49
48
|
"joi": "17.6.0",
|
|
50
49
|
"jsonwebtoken": "9.0.2",
|
|
51
|
-
"knex": "2.4.2",
|
|
52
50
|
"koa": "2.15.4",
|
|
53
51
|
"koa-body": "4.2.0",
|
|
54
52
|
"koa-compress": "4.0.1",
|
|
55
|
-
"koa-passport": "4.1.4",
|
|
56
53
|
"koa-redis": "^4.0.1",
|
|
57
|
-
"koa-send": "5.0.1",
|
|
58
54
|
"koa-session": "5.13.1",
|
|
59
|
-
"koa-static": "5.0.0",
|
|
60
55
|
"koa-useragent": "^4.1.0",
|
|
61
56
|
"lodash": "4.17.23",
|
|
62
57
|
"marked": "^15.0.11",
|
|
63
58
|
"node-fetch": "2.6.7",
|
|
64
59
|
"nodemailer": "7.0.11",
|
|
65
|
-
"passport-google-oauth": "2.0.0",
|
|
66
|
-
"passport-local": "1.0.0",
|
|
67
60
|
"pouchdb": "9.0.0",
|
|
68
|
-
"pouchdb-all-dbs": "1.1.1",
|
|
69
61
|
"scim-patch": "^0.8.1",
|
|
70
62
|
"scim2-parse-filter": "^0.2.8",
|
|
71
|
-
"server-destroy": "1.0.1",
|
|
72
|
-
"undici": "^7.16.0",
|
|
73
63
|
"uuid": "^8.3.2",
|
|
74
64
|
"yaml": "^2.8.2"
|
|
75
65
|
},
|
|
@@ -79,7 +69,6 @@
|
|
|
79
69
|
"@types/maildev": "^0.0.7",
|
|
80
70
|
"@types/node-fetch": "2.6.4",
|
|
81
71
|
"@types/nodemailer": "^6.4.17",
|
|
82
|
-
"@types/server-destroy": "1.0.1",
|
|
83
72
|
"@types/supertest": "2.0.14",
|
|
84
73
|
"@types/uuid": "8.3.4",
|
|
85
74
|
"cheerio": "^1.0.0",
|
|
@@ -91,23 +80,5 @@
|
|
|
91
80
|
"supertest": "6.3.3",
|
|
92
81
|
"timekeeper": "2.2.0"
|
|
93
82
|
},
|
|
94
|
-
"
|
|
95
|
-
"@budibase/pro": "npm:@budibase/pro@latest"
|
|
96
|
-
},
|
|
97
|
-
"nx": {
|
|
98
|
-
"targets": {
|
|
99
|
-
"dev": {
|
|
100
|
-
"dependsOn": [
|
|
101
|
-
{
|
|
102
|
-
"comment": "Required for pro usage when submodule not loaded",
|
|
103
|
-
"projects": [
|
|
104
|
-
"@budibase/backend-core"
|
|
105
|
-
],
|
|
106
|
-
"target": "build:oss"
|
|
107
|
-
}
|
|
108
|
-
]
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
"gitHead": "9de72ce9dd1c290eb3d16fd22693192923f46f06"
|
|
83
|
+
"gitHead": "7f5947eadcf2e540ae7e9334ef8f160be64fea2e"
|
|
113
84
|
}
|
|
@@ -514,9 +514,6 @@ export async function find(ctx: UserCtx<void, FindConfigResponse>) {
|
|
|
514
514
|
case ConfigType.OIDC_LOGOS:
|
|
515
515
|
await enrichOIDCLogos(config)
|
|
516
516
|
break
|
|
517
|
-
case ConfigType.AI:
|
|
518
|
-
await pro.sdk.ai.enrichAIConfig(config)
|
|
519
|
-
break
|
|
520
517
|
}
|
|
521
518
|
|
|
522
519
|
stripSecrets(config, ctx)
|
|
@@ -9,8 +9,13 @@ import {
|
|
|
9
9
|
tenancy,
|
|
10
10
|
users,
|
|
11
11
|
} from "@budibase/backend-core"
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { db as proDb } from "@budibase/pro"
|
|
13
|
+
import {
|
|
14
|
+
BpmStatusKey,
|
|
15
|
+
BpmStatusValue,
|
|
16
|
+
dataFilters,
|
|
17
|
+
utils,
|
|
18
|
+
} from "@budibase/shared-core"
|
|
14
19
|
import {
|
|
15
20
|
AcceptUserInviteRequest,
|
|
16
21
|
AcceptUserInviteResponse,
|
|
@@ -50,6 +55,7 @@ import {
|
|
|
50
55
|
UnsavedUser,
|
|
51
56
|
UpdateInviteResponse,
|
|
52
57
|
User,
|
|
58
|
+
UserGroup,
|
|
53
59
|
UserCtx,
|
|
54
60
|
UserIdentifier,
|
|
55
61
|
} from "@budibase/types"
|
|
@@ -370,12 +376,66 @@ const searchWorkspaceUsers = async (
|
|
|
370
376
|
return { data: [], hasNextPage: false }
|
|
371
377
|
}
|
|
372
378
|
|
|
379
|
+
const prodWorkspaceId = db.getProdWorkspaceID(workspaceId)
|
|
373
380
|
const limit = body.limit ?? DEFAULT_USER_LIMIT
|
|
374
381
|
const query = body.query
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
382
|
+
const globalDb = context.getGlobalDB()
|
|
383
|
+
|
|
384
|
+
const [workspaceUsers, globalPermissionUsers, workspaceGroups] =
|
|
385
|
+
await Promise.all([
|
|
386
|
+
db.queryGlobalView<User>(
|
|
387
|
+
db.ViewName.USER_BY_WORKSPACE,
|
|
388
|
+
db.getUsersByWorkspaceParams(prodWorkspaceId, {
|
|
389
|
+
include_docs: true,
|
|
390
|
+
}),
|
|
391
|
+
undefined,
|
|
392
|
+
{ arrayResponse: true }
|
|
393
|
+
) as Promise<User[]>,
|
|
394
|
+
globalDb
|
|
395
|
+
.find<User>({
|
|
396
|
+
selector: {
|
|
397
|
+
_id: {
|
|
398
|
+
$regex: `^${db.DocumentType.USER}${db.SEPARATOR}`,
|
|
399
|
+
},
|
|
400
|
+
$or: [{ "admin.global": true }, { "builder.global": true }],
|
|
401
|
+
},
|
|
402
|
+
})
|
|
403
|
+
.then(response => response.docs),
|
|
404
|
+
globalDb
|
|
405
|
+
.allDocs<UserGroup>(
|
|
406
|
+
db.getDocParams(db.DocumentType.GROUP, null, {
|
|
407
|
+
include_docs: true,
|
|
408
|
+
})
|
|
409
|
+
)
|
|
410
|
+
.then(
|
|
411
|
+
response =>
|
|
412
|
+
response.rows
|
|
413
|
+
.map(row => row.doc)
|
|
414
|
+
.filter(group => !!group?.roles?.[prodWorkspaceId]) as UserGroup[]
|
|
415
|
+
),
|
|
416
|
+
])
|
|
417
|
+
|
|
418
|
+
const workspaceGroupIds = workspaceGroups.map(group => group._id!)
|
|
419
|
+
const workspaceGroupIdSet = new Set(workspaceGroupIds)
|
|
420
|
+
|
|
421
|
+
let groupUsers: User[] = []
|
|
422
|
+
if (workspaceGroupIds.length) {
|
|
423
|
+
const usersByGroup = await Promise.all(
|
|
424
|
+
workspaceGroupIds.map(groupId => proDb.groups.getGroupUsers(groupId))
|
|
425
|
+
)
|
|
426
|
+
const groupUserIds = [
|
|
427
|
+
...new Set(
|
|
428
|
+
usersByGroup
|
|
429
|
+
.flat()
|
|
430
|
+
.map(user => user._id)
|
|
431
|
+
.filter(Boolean)
|
|
432
|
+
),
|
|
433
|
+
] as string[]
|
|
434
|
+
|
|
435
|
+
if (groupUserIds.length) {
|
|
436
|
+
groupUsers = await userSdk.db.bulkGet(groupUserIds)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
379
439
|
|
|
380
440
|
const getBookmarkValue = (user: User) => {
|
|
381
441
|
if (query?.string?.email && user.email) {
|
|
@@ -384,73 +444,54 @@ const searchWorkspaceUsers = async (
|
|
|
384
444
|
return user._id
|
|
385
445
|
}
|
|
386
446
|
|
|
387
|
-
const hydrateGroupAccess = async (usersToCheck: User[]) => {
|
|
388
|
-
const missingGroupIds = new Set<string>()
|
|
389
|
-
for (const user of usersToCheck) {
|
|
390
|
-
for (const groupId of user.userGroups || []) {
|
|
391
|
-
if (!groupAccessCache.has(groupId)) {
|
|
392
|
-
missingGroupIds.add(groupId)
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (!missingGroupIds.size) {
|
|
398
|
-
return
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const groupIdList = [...missingGroupIds]
|
|
402
|
-
const groups = await proGroups.getBulk(groupIdList, { enriched: false })
|
|
403
|
-
groups.forEach((group, index) => {
|
|
404
|
-
const groupId = group?._id || groupIdList[index]
|
|
405
|
-
const hasAccess = !!group?.roles?.[workspaceId]
|
|
406
|
-
groupAccessCache.set(groupId, hasAccess)
|
|
407
|
-
})
|
|
408
|
-
}
|
|
409
|
-
|
|
410
447
|
const hasWorkspaceAccess = (user: User) => {
|
|
411
|
-
if (users.isAdminOrBuilder(user,
|
|
448
|
+
if (users.isAdminOrBuilder(user, prodWorkspaceId)) {
|
|
412
449
|
return true
|
|
413
450
|
}
|
|
414
|
-
if (user.roles?.[
|
|
451
|
+
if (user.roles?.[prodWorkspaceId]) {
|
|
415
452
|
return true
|
|
416
453
|
}
|
|
417
454
|
return (user.userGroups || []).some(groupId =>
|
|
418
|
-
|
|
455
|
+
workspaceGroupIdSet.has(groupId)
|
|
419
456
|
)
|
|
420
457
|
}
|
|
421
458
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
break
|
|
459
|
+
const dedupedUsers = new Map<string, User>()
|
|
460
|
+
for (const user of [
|
|
461
|
+
...workspaceUsers,
|
|
462
|
+
...globalPermissionUsers,
|
|
463
|
+
...groupUsers,
|
|
464
|
+
]) {
|
|
465
|
+
if (user?._id) {
|
|
466
|
+
dedupedUsers.set(user._id, user)
|
|
431
467
|
}
|
|
468
|
+
}
|
|
432
469
|
|
|
433
|
-
|
|
470
|
+
let filtered = [...dedupedUsers.values()].filter(hasWorkspaceAccess)
|
|
434
471
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
filtered.push(user)
|
|
440
|
-
if (filtered.length === limit + 1) {
|
|
441
|
-
nextPage = getBookmarkValue(user)
|
|
442
|
-
break
|
|
443
|
-
}
|
|
444
|
-
}
|
|
472
|
+
if (query) {
|
|
473
|
+
filtered = dataFilters.search(filtered, { query }).rows
|
|
474
|
+
}
|
|
445
475
|
|
|
446
|
-
|
|
447
|
-
|
|
476
|
+
filtered.sort((userA, userB) => {
|
|
477
|
+
if (query?.string?.email) {
|
|
478
|
+
const emailA = userA.email?.toLowerCase() || ""
|
|
479
|
+
const emailB = userB.email?.toLowerCase() || ""
|
|
480
|
+
if (emailA !== emailB) {
|
|
481
|
+
return emailA.localeCompare(emailB)
|
|
482
|
+
}
|
|
448
483
|
}
|
|
449
|
-
|
|
450
|
-
}
|
|
484
|
+
return (userA._id || "").localeCompare(userB._id || "")
|
|
485
|
+
})
|
|
451
486
|
|
|
452
|
-
const
|
|
453
|
-
const
|
|
487
|
+
const bookmark = body.bookmark
|
|
488
|
+
const pagedUsers = bookmark
|
|
489
|
+
? filtered.filter(user => (getBookmarkValue(user) || "") >= bookmark)
|
|
490
|
+
: filtered
|
|
491
|
+
const pageData = pagedUsers.slice(0, limit + 1)
|
|
492
|
+
const hasNextPage = pageData.length > limit
|
|
493
|
+
const data = hasNextPage ? pageData.slice(0, limit) : pageData
|
|
494
|
+
const nextPage = hasNextPage ? getBookmarkValue(pageData[limit]) : undefined
|
|
454
495
|
|
|
455
496
|
for (let user of data) {
|
|
456
497
|
if (user) {
|
|
@@ -2,6 +2,7 @@ import { InviteUsersResponse, OIDCUser, User } from "@budibase/types"
|
|
|
2
2
|
|
|
3
3
|
import { accounts as _accounts, events, tenancy } from "@budibase/backend-core"
|
|
4
4
|
import { mocks as featureMocks } from "@budibase/backend-core/tests"
|
|
5
|
+
import { sdk as proSdk } from "@budibase/pro"
|
|
5
6
|
import * as userSdk from "../../../../sdk/users"
|
|
6
7
|
import { TestConfiguration, mocks, structures } from "../../../../tests"
|
|
7
8
|
|
|
@@ -919,6 +920,49 @@ describe("/api/global/users", () => {
|
|
|
919
920
|
expect(response.body.data.length).toBe(0)
|
|
920
921
|
})
|
|
921
922
|
|
|
923
|
+
it("should include users with workspace access via groups", async () => {
|
|
924
|
+
const workspaceId = "app_workspace_filter_group"
|
|
925
|
+
const email = structures.users.newEmail()
|
|
926
|
+
featureMocks.licenses.useUnlimited()
|
|
927
|
+
const group = await config.doInTenant(() =>
|
|
928
|
+
proSdk.groups.save({
|
|
929
|
+
...structures.groups.UserGroup(),
|
|
930
|
+
roles: { [workspaceId]: "BASIC" },
|
|
931
|
+
})
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
await config.createUser({
|
|
935
|
+
email,
|
|
936
|
+
userGroups: [group.id],
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
const response = await config.api.users.searchUsers({
|
|
940
|
+
workspaceId,
|
|
941
|
+
query: { string: { email } },
|
|
942
|
+
})
|
|
943
|
+
|
|
944
|
+
expect(response.body.data.length).toBe(1)
|
|
945
|
+
expect(response.body.data[0].email).toBe(email)
|
|
946
|
+
})
|
|
947
|
+
|
|
948
|
+
it("should include global admins in workspace search", async () => {
|
|
949
|
+
const workspaceId = "app_workspace_filter_global_admin"
|
|
950
|
+
const email = structures.users.newEmail()
|
|
951
|
+
await config.createUser({
|
|
952
|
+
email,
|
|
953
|
+
admin: { global: true },
|
|
954
|
+
builder: { global: true },
|
|
955
|
+
})
|
|
956
|
+
|
|
957
|
+
const response = await config.api.users.searchUsers({
|
|
958
|
+
workspaceId,
|
|
959
|
+
query: { string: { email } },
|
|
960
|
+
})
|
|
961
|
+
|
|
962
|
+
expect(response.body.data.length).toBe(1)
|
|
963
|
+
expect(response.body.data[0].email).toBe(email)
|
|
964
|
+
})
|
|
965
|
+
|
|
922
966
|
it("should return no users when workspaceId is empty", async () => {
|
|
923
967
|
const email = structures.users.newEmail()
|
|
924
968
|
await config.createUser({ email })
|
package/src/environment.ts
CHANGED
|
@@ -101,9 +101,6 @@ const environment = {
|
|
|
101
101
|
PASSWORD_RESET_RATE_IP_WINDOW_SECONDS:
|
|
102
102
|
parseIntSafe(process.env.PASSWORD_RESET_RATE_IP_WINDOW_SECONDS) || 900,
|
|
103
103
|
|
|
104
|
-
// Budibase AI
|
|
105
|
-
BUDIBASE_AI_API_KEY: process.env.BUDIBASE_AI_API_KEY,
|
|
106
|
-
BUDIBASE_AI_DEFAULT_MODEL: process.env.BUDIBASE_AI_DEFAULT_MODEL,
|
|
107
104
|
_set(key: any, value: any) {
|
|
108
105
|
process.env[key] = value
|
|
109
106
|
// @ts-ignore
|