@budibase/worker 2.33.3 → 2.33.5
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/users.ts +28 -6
- package/src/api/index.ts +1 -9
- 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.5",
|
|
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.5",
|
|
41
|
+
"@budibase/pro": "2.33.5",
|
|
42
|
+
"@budibase/string-templates": "2.33.5",
|
|
43
|
+
"@budibase/types": "2.33.5",
|
|
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": "44bd5d95d92702fb1d7c01f32b56920523c11da2"
|
|
111
111
|
}
|
|
@@ -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
|
*/
|
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
|
]
|
|
@@ -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
|
-
})
|