@budibase/worker 3.25.4 → 3.26.0
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/worker",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.26.0",
|
|
5
5
|
"description": "Budibase background service",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -108,5 +108,5 @@
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
},
|
|
111
|
-
"gitHead": "
|
|
111
|
+
"gitHead": "94f6d3fcbeb2043d8144dc3be0cf560b71dd35dc"
|
|
112
112
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
tenancy,
|
|
11
11
|
users,
|
|
12
12
|
} from "@budibase/backend-core"
|
|
13
|
-
import { features } from "@budibase/pro"
|
|
13
|
+
import { features, groups as proGroups } from "@budibase/pro"
|
|
14
14
|
import { BpmStatusKey, BpmStatusValue, utils } from "@budibase/shared-core"
|
|
15
15
|
import {
|
|
16
16
|
AcceptUserInviteRequest,
|
|
@@ -318,6 +318,18 @@ export const search = async (
|
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
const hasWorkspaceId =
|
|
322
|
+
body && Object.prototype.hasOwnProperty.call(body, "workspaceId")
|
|
323
|
+
|
|
324
|
+
if (hasWorkspaceId) {
|
|
325
|
+
let response = await searchWorkspaceUsers(body)
|
|
326
|
+
if (!users.hasBuilderPermissions(ctx.user)) {
|
|
327
|
+
response.data = stripUsers(response.data)
|
|
328
|
+
}
|
|
329
|
+
ctx.body = response
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
|
|
321
333
|
let response: SearchUsersResponse = { data: [] }
|
|
322
334
|
|
|
323
335
|
if (body.paginate === false) {
|
|
@@ -347,6 +359,111 @@ export const search = async (
|
|
|
347
359
|
ctx.body = response
|
|
348
360
|
}
|
|
349
361
|
|
|
362
|
+
const DEFAULT_USER_LIMIT = 8
|
|
363
|
+
|
|
364
|
+
const searchWorkspaceUsers = async (
|
|
365
|
+
body: SearchUsersRequest
|
|
366
|
+
): Promise<SearchUsersResponse> => {
|
|
367
|
+
const workspaceId = body.workspaceId
|
|
368
|
+
if (!workspaceId) {
|
|
369
|
+
return { data: [], hasNextPage: false }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const limit = body.limit ?? DEFAULT_USER_LIMIT
|
|
373
|
+
const query = body.query
|
|
374
|
+
const filtered: User[] = []
|
|
375
|
+
let cursor = body.bookmark
|
|
376
|
+
let nextPage: string | undefined
|
|
377
|
+
const groupAccessCache = new Map<string, boolean>()
|
|
378
|
+
|
|
379
|
+
const getBookmarkValue = (user: User) => {
|
|
380
|
+
if (query?.string?.email && user.email) {
|
|
381
|
+
return user.email.toLowerCase()
|
|
382
|
+
}
|
|
383
|
+
return user._id
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const hydrateGroupAccess = async (usersToCheck: User[]) => {
|
|
387
|
+
const missingGroupIds = new Set<string>()
|
|
388
|
+
for (const user of usersToCheck) {
|
|
389
|
+
for (const groupId of user.userGroups || []) {
|
|
390
|
+
if (!groupAccessCache.has(groupId)) {
|
|
391
|
+
missingGroupIds.add(groupId)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (!missingGroupIds.size) {
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const groupIdList = [...missingGroupIds]
|
|
401
|
+
const groups = await proGroups.getBulk(groupIdList, { enriched: false })
|
|
402
|
+
groups.forEach((group, index) => {
|
|
403
|
+
const groupId = group?._id || groupIdList[index]
|
|
404
|
+
const hasAccess = !!group?.roles?.[workspaceId]
|
|
405
|
+
groupAccessCache.set(groupId, hasAccess)
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const hasWorkspaceAccess = (user: User) => {
|
|
410
|
+
if (users.isAdminOrBuilder(user, workspaceId)) {
|
|
411
|
+
return true
|
|
412
|
+
}
|
|
413
|
+
if (user.roles?.[workspaceId]) {
|
|
414
|
+
return true
|
|
415
|
+
}
|
|
416
|
+
return (user.userGroups || []).some(groupId =>
|
|
417
|
+
groupAccessCache.get(groupId)
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
while (filtered.length <= limit) {
|
|
422
|
+
const page = await userSdk.core.paginatedUsers({
|
|
423
|
+
bookmark: cursor,
|
|
424
|
+
query,
|
|
425
|
+
limit,
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
if (!page.data?.length) {
|
|
429
|
+
break
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
await hydrateGroupAccess(page.data)
|
|
433
|
+
|
|
434
|
+
for (const user of page.data) {
|
|
435
|
+
if (!hasWorkspaceAccess(user)) {
|
|
436
|
+
continue
|
|
437
|
+
}
|
|
438
|
+
filtered.push(user)
|
|
439
|
+
if (filtered.length === limit + 1) {
|
|
440
|
+
nextPage = getBookmarkValue(user)
|
|
441
|
+
break
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (filtered.length === limit + 1 || !page.hasNextPage) {
|
|
446
|
+
break
|
|
447
|
+
}
|
|
448
|
+
cursor = page.nextPage
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const hasNextPage = filtered.length > limit
|
|
452
|
+
const data = hasNextPage ? filtered.slice(0, limit) : filtered
|
|
453
|
+
|
|
454
|
+
for (let user of data) {
|
|
455
|
+
if (user) {
|
|
456
|
+
delete user.password
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
data,
|
|
462
|
+
hasNextPage,
|
|
463
|
+
nextPage: hasNextPage ? nextPage : undefined,
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
350
467
|
// called internally by app server user fetch
|
|
351
468
|
export const fetch = async (ctx: UserCtx<void, FetchUsersResponse>) => {
|
|
352
469
|
const all = await userSdk.db.allUsers()
|
|
@@ -868,6 +868,52 @@ describe("/api/global/users", () => {
|
|
|
868
868
|
expect(response.body.data[0].email).toBe(email)
|
|
869
869
|
})
|
|
870
870
|
|
|
871
|
+
it("should filter by workspace access when workspaceId is provided", async () => {
|
|
872
|
+
const workspaceId = "app_workspace_filter"
|
|
873
|
+
const email = structures.users.newEmail()
|
|
874
|
+
await config.createUser({
|
|
875
|
+
email,
|
|
876
|
+
roles: { [workspaceId]: "BASIC" },
|
|
877
|
+
})
|
|
878
|
+
|
|
879
|
+
const response = await config.api.users.searchUsers({
|
|
880
|
+
workspaceId,
|
|
881
|
+
query: { string: { email } },
|
|
882
|
+
})
|
|
883
|
+
|
|
884
|
+
expect(response.body.data.length).toBe(1)
|
|
885
|
+
expect(response.body.data[0].email).toBe(email)
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
it("should exclude users without workspace access", async () => {
|
|
889
|
+
const workspaceId = "app_workspace_filter_exclude"
|
|
890
|
+
const email = structures.users.newEmail()
|
|
891
|
+
await config.createUser({
|
|
892
|
+
email,
|
|
893
|
+
roles: { app_other: "BASIC" },
|
|
894
|
+
})
|
|
895
|
+
|
|
896
|
+
const response = await config.api.users.searchUsers({
|
|
897
|
+
workspaceId,
|
|
898
|
+
query: { string: { email } },
|
|
899
|
+
})
|
|
900
|
+
|
|
901
|
+
expect(response.body.data.length).toBe(0)
|
|
902
|
+
})
|
|
903
|
+
|
|
904
|
+
it("should return no users when workspaceId is empty", async () => {
|
|
905
|
+
const email = structures.users.newEmail()
|
|
906
|
+
await config.createUser({ email })
|
|
907
|
+
|
|
908
|
+
const response = await config.api.users.searchUsers({
|
|
909
|
+
workspaceId: "",
|
|
910
|
+
query: { string: { email } },
|
|
911
|
+
})
|
|
912
|
+
|
|
913
|
+
expect(response.body.data.length).toBe(0)
|
|
914
|
+
expect(response.body.hasNextPage).toBe(false)
|
|
915
|
+
})
|
|
916
|
+
|
|
871
917
|
it("should be able to search by email with numeric prefixing", async () => {
|
|
872
918
|
const user = await config.createUser()
|
|
873
919
|
const response = await config.api.users.searchUsers({
|
package/src/tests/api/users.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
CreateAdminUserRequest,
|
|
6
6
|
InviteUsersRequest,
|
|
7
7
|
InviteUsersResponse,
|
|
8
|
-
|
|
8
|
+
SearchUsersRequest,
|
|
9
9
|
User,
|
|
10
10
|
} from "@budibase/types"
|
|
11
11
|
import structures from "../structures"
|
|
@@ -151,7 +151,7 @@ export class UserAPI extends TestAPI {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
searchUsers = (
|
|
154
|
-
|
|
154
|
+
body: SearchUsersRequest = {},
|
|
155
155
|
opts?: {
|
|
156
156
|
status?: number
|
|
157
157
|
noHeaders?: boolean
|
|
@@ -160,7 +160,7 @@ export class UserAPI extends TestAPI {
|
|
|
160
160
|
) => {
|
|
161
161
|
const req = this.request
|
|
162
162
|
.post("/api/global/users/search")
|
|
163
|
-
.send(
|
|
163
|
+
.send(body)
|
|
164
164
|
.expect("Content-Type", /json/)
|
|
165
165
|
.expect(opts?.status ? opts.status : 200)
|
|
166
166
|
if (opts?.useHeaders) {
|