@budibase/worker 1.2.58 → 1.3.1
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 +7 -6
- package/scripts/jestSetup.js +6 -0
- package/src/api/controllers/global/auth.ts +14 -1
- package/src/api/controllers/global/users.ts +26 -55
- package/src/api/controllers/system/accounts.ts +21 -0
- package/src/api/{index.js → index.ts} +12 -16
- package/src/api/routes/global/configs.js +1 -0
- package/src/api/routes/{tests/auth.spec.js → global/tests/auth.spec.ts} +32 -56
- package/src/api/routes/{tests/configs.spec.js → global/tests/configs.spec.ts} +76 -47
- package/src/api/routes/{tests/email.spec.js → global/tests/email.spec.ts} +7 -15
- package/src/api/routes/{tests/realEmail.spec.js → global/tests/realEmail.spec.ts} +20 -21
- package/src/api/routes/{tests/self.spec.js → global/tests/self.spec.ts} +10 -18
- package/src/api/routes/global/tests/users.spec.ts +512 -0
- package/src/api/routes/index.js +2 -0
- package/src/api/routes/system/accounts.ts +19 -0
- package/src/api/routes/system/tests/accounts.spec.ts +57 -0
- package/src/{environment.js → environment.ts} +10 -9
- package/src/index.ts +6 -4
- package/src/sdk/accounts/accounts.ts +53 -0
- package/src/sdk/accounts/index.ts +1 -0
- package/src/sdk/index.ts +1 -0
- package/src/sdk/users/users.ts +271 -77
- package/src/tests/TestConfiguration.ts +273 -0
- package/src/tests/api/accounts.ts +28 -0
- package/src/tests/api/auth.ts +48 -0
- package/src/tests/api/configs.ts +40 -0
- package/src/tests/api/email.ts +24 -0
- package/src/tests/api/index.ts +25 -0
- package/src/tests/api/self.ts +21 -0
- package/src/tests/api/users.ts +111 -0
- package/src/tests/index.ts +14 -0
- package/src/tests/mocks/index.ts +7 -0
- package/src/tests/structures/accounts.ts +24 -0
- package/src/tests/structures/index.ts +20 -0
- package/src/tests/structures/users.ts +12 -4
- package/src/utilities/redis.js +1 -0
- package/scripts/load/users.js +0 -97
- package/src/api/routes/tests/users.spec.js +0 -390
- package/src/tests/TestConfiguration.js +0 -231
- package/src/tests/index.js +0 -12
- package/src/tests/mocks/index.js +0 -5
- package/src/tests/structures/index.js +0 -14
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { InviteUsersResponse } from "@budibase/types"
|
|
2
|
+
|
|
3
|
+
jest.mock("nodemailer")
|
|
4
|
+
import {
|
|
5
|
+
TestConfiguration,
|
|
6
|
+
mocks,
|
|
7
|
+
structures,
|
|
8
|
+
TENANT_1,
|
|
9
|
+
API,
|
|
10
|
+
} from "../../../../tests"
|
|
11
|
+
const sendMailMock = mocks.email.mock()
|
|
12
|
+
import { events, tenancy } from "@budibase/backend-core"
|
|
13
|
+
|
|
14
|
+
describe("/api/global/users", () => {
|
|
15
|
+
const config = new TestConfiguration()
|
|
16
|
+
const api = new API(config)
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
await config.beforeAll()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await config.afterAll()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
jest.clearAllMocks()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe("invite", () => {
|
|
31
|
+
it("should be able to generate an invitation", async () => {
|
|
32
|
+
const email = structures.users.newEmail()
|
|
33
|
+
const { code, res } = await api.users.sendUserInvite(sendMailMock, email)
|
|
34
|
+
|
|
35
|
+
expect(res.body).toEqual({ message: "Invitation has been sent." })
|
|
36
|
+
expect(sendMailMock).toHaveBeenCalled()
|
|
37
|
+
expect(code).toBeDefined()
|
|
38
|
+
expect(events.user.invited).toBeCalledTimes(1)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("should not be able to generate an invitation for existing user", async () => {
|
|
42
|
+
const { code, res } = await api.users.sendUserInvite(
|
|
43
|
+
sendMailMock,
|
|
44
|
+
config.defaultUser!.email,
|
|
45
|
+
400
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
expect(res.body.message).toBe("Unavailable")
|
|
49
|
+
expect(sendMailMock).toHaveBeenCalledTimes(0)
|
|
50
|
+
expect(code).toBeUndefined()
|
|
51
|
+
expect(events.user.invited).toBeCalledTimes(0)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("should be able to create new user from invite", async () => {
|
|
55
|
+
const email = structures.users.newEmail()
|
|
56
|
+
const { code } = await api.users.sendUserInvite(sendMailMock, email)
|
|
57
|
+
|
|
58
|
+
const res = await api.users.acceptInvite(code)
|
|
59
|
+
|
|
60
|
+
expect(res.body._id).toBeDefined()
|
|
61
|
+
const user = await config.getUser(email)
|
|
62
|
+
expect(user).toBeDefined()
|
|
63
|
+
expect(user._id).toEqual(res.body._id)
|
|
64
|
+
expect(events.user.inviteAccepted).toBeCalledTimes(1)
|
|
65
|
+
expect(events.user.inviteAccepted).toBeCalledWith(user)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe("inviteMultiple", () => {
|
|
70
|
+
it("should be able to generate an invitation", async () => {
|
|
71
|
+
const newUserInvite = () => ({
|
|
72
|
+
email: structures.users.newEmail(),
|
|
73
|
+
userInfo: {},
|
|
74
|
+
})
|
|
75
|
+
const request = [newUserInvite(), newUserInvite()]
|
|
76
|
+
|
|
77
|
+
const res = await api.users.sendMultiUserInvite(request)
|
|
78
|
+
|
|
79
|
+
const body = res.body as InviteUsersResponse
|
|
80
|
+
expect(body.successful.length).toBe(2)
|
|
81
|
+
expect(body.unsuccessful.length).toBe(0)
|
|
82
|
+
expect(sendMailMock).toHaveBeenCalledTimes(2)
|
|
83
|
+
expect(events.user.invited).toBeCalledTimes(2)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it("should not be able to generate an invitation for existing user", async () => {
|
|
87
|
+
const request = [{ email: config.defaultUser!.email, userInfo: {} }]
|
|
88
|
+
|
|
89
|
+
const res = await api.users.sendMultiUserInvite(request)
|
|
90
|
+
|
|
91
|
+
const body = res.body as InviteUsersResponse
|
|
92
|
+
expect(body.successful.length).toBe(0)
|
|
93
|
+
expect(body.unsuccessful.length).toBe(1)
|
|
94
|
+
expect(body.unsuccessful[0].reason).toBe("Unavailable")
|
|
95
|
+
expect(sendMailMock).toHaveBeenCalledTimes(0)
|
|
96
|
+
expect(events.user.invited).toBeCalledTimes(0)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe("bulkCreate", () => {
|
|
101
|
+
it("should ignore users existing in the same tenant", async () => {
|
|
102
|
+
const user = await config.createUser()
|
|
103
|
+
jest.clearAllMocks()
|
|
104
|
+
|
|
105
|
+
const response = await api.users.bulkCreateUsers([user])
|
|
106
|
+
|
|
107
|
+
expect(response.successful.length).toBe(0)
|
|
108
|
+
expect(response.unsuccessful.length).toBe(1)
|
|
109
|
+
expect(response.unsuccessful[0].email).toBe(user.email)
|
|
110
|
+
expect(events.user.created).toBeCalledTimes(0)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it("should ignore users existing in other tenants", async () => {
|
|
114
|
+
const user = await config.createUser()
|
|
115
|
+
jest.resetAllMocks()
|
|
116
|
+
|
|
117
|
+
await tenancy.doInTenant(TENANT_1, async () => {
|
|
118
|
+
const response = await api.users.bulkCreateUsers([user])
|
|
119
|
+
|
|
120
|
+
expect(response.successful.length).toBe(0)
|
|
121
|
+
expect(response.unsuccessful.length).toBe(1)
|
|
122
|
+
expect(response.unsuccessful[0].email).toBe(user.email)
|
|
123
|
+
expect(events.user.created).toBeCalledTimes(0)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it("should ignore accounts using the same email", async () => {
|
|
128
|
+
const account = structures.accounts.account()
|
|
129
|
+
const resp = await api.accounts.saveMetadata(account)
|
|
130
|
+
const user = structures.users.user({ email: resp.email })
|
|
131
|
+
jest.clearAllMocks()
|
|
132
|
+
|
|
133
|
+
const response = await api.users.bulkCreateUsers([user])
|
|
134
|
+
|
|
135
|
+
expect(response.successful.length).toBe(0)
|
|
136
|
+
expect(response.unsuccessful.length).toBe(1)
|
|
137
|
+
expect(response.unsuccessful[0].email).toBe(user.email)
|
|
138
|
+
expect(events.user.created).toBeCalledTimes(0)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it("should be able to bulkCreate users", async () => {
|
|
142
|
+
const builder = structures.users.builderUser()
|
|
143
|
+
const admin = structures.users.adminUser()
|
|
144
|
+
const user = structures.users.user()
|
|
145
|
+
|
|
146
|
+
const response = await api.users.bulkCreateUsers([builder, admin, user])
|
|
147
|
+
|
|
148
|
+
expect(response.successful.length).toBe(3)
|
|
149
|
+
expect(response.successful[0].email).toBe(builder.email)
|
|
150
|
+
expect(response.successful[1].email).toBe(admin.email)
|
|
151
|
+
expect(response.successful[2].email).toBe(user.email)
|
|
152
|
+
expect(response.unsuccessful.length).toBe(0)
|
|
153
|
+
expect(events.user.created).toBeCalledTimes(3)
|
|
154
|
+
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1)
|
|
155
|
+
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(2)
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
describe("create", () => {
|
|
160
|
+
it("should be able to create a basic user", async () => {
|
|
161
|
+
const user = structures.users.user()
|
|
162
|
+
|
|
163
|
+
await api.users.saveUser(user)
|
|
164
|
+
|
|
165
|
+
expect(events.user.created).toBeCalledTimes(1)
|
|
166
|
+
expect(events.user.updated).not.toBeCalled()
|
|
167
|
+
expect(events.user.permissionBuilderAssigned).not.toBeCalled()
|
|
168
|
+
expect(events.user.permissionAdminAssigned).not.toBeCalled()
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it("should be able to create an admin user", async () => {
|
|
172
|
+
const user = structures.users.adminUser()
|
|
173
|
+
|
|
174
|
+
await api.users.saveUser(user)
|
|
175
|
+
|
|
176
|
+
expect(events.user.created).toBeCalledTimes(1)
|
|
177
|
+
expect(events.user.updated).not.toBeCalled()
|
|
178
|
+
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1)
|
|
179
|
+
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it("should be able to create a builder user", async () => {
|
|
183
|
+
const user = structures.users.builderUser()
|
|
184
|
+
|
|
185
|
+
await api.users.saveUser(user)
|
|
186
|
+
|
|
187
|
+
expect(events.user.created).toBeCalledTimes(1)
|
|
188
|
+
expect(events.user.updated).not.toBeCalled()
|
|
189
|
+
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1)
|
|
190
|
+
expect(events.user.permissionAdminAssigned).not.toBeCalled()
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it("should be able to assign app roles", async () => {
|
|
194
|
+
const user = structures.users.user()
|
|
195
|
+
user.roles = {
|
|
196
|
+
app_123: "role1",
|
|
197
|
+
app_456: "role2",
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await api.users.saveUser(user)
|
|
201
|
+
|
|
202
|
+
const savedUser = await config.getUser(user.email)
|
|
203
|
+
expect(events.user.created).toBeCalledTimes(1)
|
|
204
|
+
expect(events.user.updated).not.toBeCalled()
|
|
205
|
+
expect(events.role.assigned).toBeCalledTimes(2)
|
|
206
|
+
expect(events.role.assigned).toBeCalledWith(savedUser, "role1")
|
|
207
|
+
expect(events.role.assigned).toBeCalledWith(savedUser, "role2")
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it("should not be able to create user that exists in same tenant", async () => {
|
|
211
|
+
const user = await config.createUser()
|
|
212
|
+
jest.clearAllMocks()
|
|
213
|
+
delete user._id
|
|
214
|
+
delete user._rev
|
|
215
|
+
|
|
216
|
+
const response = await api.users.saveUser(user, 400)
|
|
217
|
+
|
|
218
|
+
expect(response.body.message).toBe(`Unavailable`)
|
|
219
|
+
expect(events.user.created).toBeCalledTimes(0)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it("should not be able to create user that exists in other tenant", async () => {
|
|
223
|
+
const user = await config.createUser()
|
|
224
|
+
jest.resetAllMocks()
|
|
225
|
+
|
|
226
|
+
await tenancy.doInTenant(TENANT_1, async () => {
|
|
227
|
+
delete user._id
|
|
228
|
+
const response = await api.users.saveUser(user, 400)
|
|
229
|
+
|
|
230
|
+
expect(response.body.message).toBe(`Unavailable`)
|
|
231
|
+
expect(events.user.created).toBeCalledTimes(0)
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it("should not be able to create user with the same email as an account", async () => {
|
|
236
|
+
const user = structures.users.user()
|
|
237
|
+
const account = structures.accounts.cloudAccount()
|
|
238
|
+
mocks.accounts.getAccount.mockReturnValueOnce(account)
|
|
239
|
+
|
|
240
|
+
const response = await api.users.saveUser(user, 400)
|
|
241
|
+
|
|
242
|
+
expect(response.body.message).toBe(`Unavailable`)
|
|
243
|
+
expect(events.user.created).toBeCalledTimes(0)
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
describe("update", () => {
|
|
248
|
+
it("should be able to update a basic user", async () => {
|
|
249
|
+
const user = await config.createUser()
|
|
250
|
+
jest.clearAllMocks()
|
|
251
|
+
|
|
252
|
+
await api.users.saveUser(user)
|
|
253
|
+
|
|
254
|
+
expect(events.user.created).not.toBeCalled()
|
|
255
|
+
expect(events.user.updated).toBeCalledTimes(1)
|
|
256
|
+
expect(events.user.permissionBuilderAssigned).not.toBeCalled()
|
|
257
|
+
expect(events.user.permissionAdminAssigned).not.toBeCalled()
|
|
258
|
+
expect(events.user.passwordForceReset).not.toBeCalled()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it("should be able to force reset password", async () => {
|
|
262
|
+
const user = await config.createUser()
|
|
263
|
+
jest.clearAllMocks()
|
|
264
|
+
|
|
265
|
+
user.forceResetPassword = true
|
|
266
|
+
user.password = "tempPassword"
|
|
267
|
+
await api.users.saveUser(user)
|
|
268
|
+
|
|
269
|
+
expect(events.user.created).not.toBeCalled()
|
|
270
|
+
expect(events.user.updated).toBeCalledTimes(1)
|
|
271
|
+
expect(events.user.permissionBuilderAssigned).not.toBeCalled()
|
|
272
|
+
expect(events.user.permissionAdminAssigned).not.toBeCalled()
|
|
273
|
+
expect(events.user.passwordForceReset).toBeCalledTimes(1)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it("should be able to update a basic user to an admin user", async () => {
|
|
277
|
+
const user = await config.createUser()
|
|
278
|
+
jest.clearAllMocks()
|
|
279
|
+
|
|
280
|
+
await api.users.saveUser(structures.users.adminUser(user))
|
|
281
|
+
|
|
282
|
+
expect(events.user.created).not.toBeCalled()
|
|
283
|
+
expect(events.user.updated).toBeCalledTimes(1)
|
|
284
|
+
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1)
|
|
285
|
+
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it("should be able to update a basic user to a builder user", async () => {
|
|
289
|
+
const user = await config.createUser()
|
|
290
|
+
jest.clearAllMocks()
|
|
291
|
+
|
|
292
|
+
await api.users.saveUser(structures.users.builderUser(user))
|
|
293
|
+
|
|
294
|
+
expect(events.user.created).not.toBeCalled()
|
|
295
|
+
expect(events.user.updated).toBeCalledTimes(1)
|
|
296
|
+
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1)
|
|
297
|
+
expect(events.user.permissionAdminAssigned).not.toBeCalled()
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it("should be able to update an admin user to a basic user", async () => {
|
|
301
|
+
const user = await config.createUser(structures.users.adminUser())
|
|
302
|
+
jest.clearAllMocks()
|
|
303
|
+
user.admin!.global = false
|
|
304
|
+
user.builder!.global = false
|
|
305
|
+
|
|
306
|
+
await api.users.saveUser(user)
|
|
307
|
+
|
|
308
|
+
expect(events.user.created).not.toBeCalled()
|
|
309
|
+
expect(events.user.updated).toBeCalledTimes(1)
|
|
310
|
+
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1)
|
|
311
|
+
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it("should be able to update an builder user to a basic user", async () => {
|
|
315
|
+
const user = await config.createUser(structures.users.builderUser())
|
|
316
|
+
jest.clearAllMocks()
|
|
317
|
+
user.builder!.global = false
|
|
318
|
+
|
|
319
|
+
await api.users.saveUser(user)
|
|
320
|
+
|
|
321
|
+
expect(events.user.created).not.toBeCalled()
|
|
322
|
+
expect(events.user.updated).toBeCalledTimes(1)
|
|
323
|
+
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
|
|
324
|
+
expect(events.user.permissionAdminRemoved).not.toBeCalled()
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it("should be able to assign app roles", async () => {
|
|
328
|
+
const user = await config.createUser()
|
|
329
|
+
jest.clearAllMocks()
|
|
330
|
+
user.roles = {
|
|
331
|
+
app_123: "role1",
|
|
332
|
+
app_456: "role2",
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
await api.users.saveUser(user)
|
|
336
|
+
|
|
337
|
+
const savedUser = await config.getUser(user.email)
|
|
338
|
+
expect(events.user.created).not.toBeCalled()
|
|
339
|
+
expect(events.user.updated).toBeCalledTimes(1)
|
|
340
|
+
expect(events.role.assigned).toBeCalledTimes(2)
|
|
341
|
+
expect(events.role.assigned).toBeCalledWith(savedUser, "role1")
|
|
342
|
+
expect(events.role.assigned).toBeCalledWith(savedUser, "role2")
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it("should be able to unassign app roles", async () => {
|
|
346
|
+
let user = structures.users.user()
|
|
347
|
+
user.roles = {
|
|
348
|
+
app_123: "role1",
|
|
349
|
+
app_456: "role2",
|
|
350
|
+
}
|
|
351
|
+
user = await config.createUser(user)
|
|
352
|
+
jest.clearAllMocks()
|
|
353
|
+
user.roles = {}
|
|
354
|
+
|
|
355
|
+
await api.users.saveUser(user)
|
|
356
|
+
|
|
357
|
+
const savedUser = await config.getUser(user.email)
|
|
358
|
+
expect(events.user.created).not.toBeCalled()
|
|
359
|
+
expect(events.user.updated).toBeCalledTimes(1)
|
|
360
|
+
expect(events.role.unassigned).toBeCalledTimes(2)
|
|
361
|
+
expect(events.role.unassigned).toBeCalledWith(savedUser, "role1")
|
|
362
|
+
expect(events.role.unassigned).toBeCalledWith(savedUser, "role2")
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it("should be able to update existing app roles", async () => {
|
|
366
|
+
let user = structures.users.user()
|
|
367
|
+
user.roles = {
|
|
368
|
+
app_123: "role1",
|
|
369
|
+
app_456: "role2",
|
|
370
|
+
}
|
|
371
|
+
user = await config.createUser(user)
|
|
372
|
+
jest.clearAllMocks()
|
|
373
|
+
user.roles = {
|
|
374
|
+
app_123: "role1",
|
|
375
|
+
app_456: "role2-edit",
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
await api.users.saveUser(user)
|
|
379
|
+
|
|
380
|
+
const savedUser = await config.getUser(user.email)
|
|
381
|
+
expect(events.user.created).not.toBeCalled()
|
|
382
|
+
expect(events.user.updated).toBeCalledTimes(1)
|
|
383
|
+
expect(events.role.unassigned).toBeCalledTimes(1)
|
|
384
|
+
expect(events.role.unassigned).toBeCalledWith(savedUser, "role2")
|
|
385
|
+
expect(events.role.assigned).toBeCalledTimes(1)
|
|
386
|
+
expect(events.role.assigned).toBeCalledWith(savedUser, "role2-edit")
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it("should not be able to update email address", async () => {
|
|
390
|
+
const email = "email@test.com"
|
|
391
|
+
const user = await config.createUser(structures.users.user({ email }))
|
|
392
|
+
user.email = "new@test.com"
|
|
393
|
+
|
|
394
|
+
const response = await api.users.saveUser(user, 400)
|
|
395
|
+
|
|
396
|
+
const dbUser = await config.getUser(email)
|
|
397
|
+
user.email = email
|
|
398
|
+
expect(user).toStrictEqual(dbUser)
|
|
399
|
+
expect(response.body.message).toBe("Email address cannot be changed")
|
|
400
|
+
})
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
describe("bulkDelete", () => {
|
|
404
|
+
it("should not be able to bulkDelete current user", async () => {
|
|
405
|
+
const user = await config.defaultUser!
|
|
406
|
+
const request = { userIds: [user._id!] }
|
|
407
|
+
|
|
408
|
+
const response = await api.users.bulkDeleteUsers(request, 400)
|
|
409
|
+
|
|
410
|
+
expect(response.body.message).toBe("Unable to delete self.")
|
|
411
|
+
expect(events.user.deleted).not.toBeCalled()
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it("should not be able to bulkDelete account owner", async () => {
|
|
415
|
+
const user = await config.createUser()
|
|
416
|
+
const account = structures.accounts.cloudAccount()
|
|
417
|
+
account.budibaseUserId = user._id!
|
|
418
|
+
mocks.accounts.getAccountByTenantId.mockReturnValue(account)
|
|
419
|
+
|
|
420
|
+
const request = { userIds: [user._id!] }
|
|
421
|
+
|
|
422
|
+
const response = await api.users.bulkDeleteUsers(request)
|
|
423
|
+
|
|
424
|
+
expect(response.body.successful.length).toBe(0)
|
|
425
|
+
expect(response.body.unsuccessful.length).toBe(1)
|
|
426
|
+
expect(response.body.unsuccessful[0].reason).toBe(
|
|
427
|
+
"Account holder cannot be deleted"
|
|
428
|
+
)
|
|
429
|
+
expect(response.body.unsuccessful[0]._id).toBe(user._id)
|
|
430
|
+
expect(events.user.deleted).not.toBeCalled()
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
it("should be able to bulk delete users", async () => {
|
|
434
|
+
const account = structures.accounts.cloudAccount()
|
|
435
|
+
mocks.accounts.getAccountByTenantId.mockReturnValue(account)
|
|
436
|
+
|
|
437
|
+
const builder = structures.users.builderUser()
|
|
438
|
+
const admin = structures.users.adminUser()
|
|
439
|
+
const user = structures.users.user()
|
|
440
|
+
const createdUsers = await api.users.bulkCreateUsers([
|
|
441
|
+
builder,
|
|
442
|
+
admin,
|
|
443
|
+
user,
|
|
444
|
+
])
|
|
445
|
+
const request = { userIds: createdUsers.successful.map(u => u._id!) }
|
|
446
|
+
|
|
447
|
+
const response = await api.users.bulkDeleteUsers(request)
|
|
448
|
+
|
|
449
|
+
expect(response.body.successful.length).toBe(3)
|
|
450
|
+
expect(response.body.unsuccessful.length).toBe(0)
|
|
451
|
+
expect(events.user.deleted).toBeCalledTimes(3)
|
|
452
|
+
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1)
|
|
453
|
+
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(2)
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
describe("destroy", () => {
|
|
458
|
+
it("should be able to destroy a basic user", async () => {
|
|
459
|
+
const user = await config.createUser()
|
|
460
|
+
jest.clearAllMocks()
|
|
461
|
+
|
|
462
|
+
await api.users.deleteUser(user._id!)
|
|
463
|
+
|
|
464
|
+
expect(events.user.deleted).toBeCalledTimes(1)
|
|
465
|
+
expect(events.user.permissionBuilderRemoved).not.toBeCalled()
|
|
466
|
+
expect(events.user.permissionAdminRemoved).not.toBeCalled()
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it("should be able to destroy an admin user", async () => {
|
|
470
|
+
const user = await config.createUser(structures.users.adminUser())
|
|
471
|
+
jest.clearAllMocks()
|
|
472
|
+
|
|
473
|
+
await api.users.deleteUser(user._id!)
|
|
474
|
+
|
|
475
|
+
expect(events.user.deleted).toBeCalledTimes(1)
|
|
476
|
+
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
|
|
477
|
+
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1)
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it("should be able to destroy a builder user", async () => {
|
|
481
|
+
const user = await config.createUser(structures.users.builderUser())
|
|
482
|
+
jest.clearAllMocks()
|
|
483
|
+
|
|
484
|
+
await api.users.deleteUser(user._id!)
|
|
485
|
+
|
|
486
|
+
expect(events.user.deleted).toBeCalledTimes(1)
|
|
487
|
+
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
|
|
488
|
+
expect(events.user.permissionAdminRemoved).not.toBeCalled()
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
it("should not be able to destroy account owner", async () => {
|
|
492
|
+
const user = await config.createUser()
|
|
493
|
+
const account = structures.accounts.cloudAccount()
|
|
494
|
+
mocks.accounts.getAccount.mockReturnValueOnce(account)
|
|
495
|
+
|
|
496
|
+
const response = await api.users.deleteUser(user._id!, 400)
|
|
497
|
+
|
|
498
|
+
expect(response.body.message).toBe("Account holder cannot be deleted")
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it("should not be able to destroy account owner as account owner", async () => {
|
|
502
|
+
const user = await config.defaultUser!
|
|
503
|
+
const account = structures.accounts.cloudAccount()
|
|
504
|
+
account.email = user.email
|
|
505
|
+
mocks.accounts.getAccount.mockReturnValueOnce(account)
|
|
506
|
+
|
|
507
|
+
const response = await api.users.deleteUser(user._id!, 400)
|
|
508
|
+
|
|
509
|
+
expect(response.body.message).toBe("Unable to delete self.")
|
|
510
|
+
})
|
|
511
|
+
})
|
|
512
|
+
})
|
package/src/api/routes/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const statusRoutes = require("./system/status")
|
|
|
12
12
|
const selfRoutes = require("./global/self")
|
|
13
13
|
const licenseRoutes = require("./global/license")
|
|
14
14
|
const migrationRoutes = require("./system/migrations")
|
|
15
|
+
const accountRoutes = require("./system/accounts")
|
|
15
16
|
|
|
16
17
|
let userGroupRoutes = api.groups
|
|
17
18
|
exports.routes = [
|
|
@@ -29,4 +30,5 @@ exports.routes = [
|
|
|
29
30
|
licenseRoutes,
|
|
30
31
|
userGroupRoutes,
|
|
31
32
|
migrationRoutes,
|
|
33
|
+
accountRoutes,
|
|
32
34
|
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Router from "@koa/router"
|
|
2
|
+
import * as controller from "../../controllers/system/accounts"
|
|
3
|
+
import { middleware } from "@budibase/backend-core"
|
|
4
|
+
|
|
5
|
+
const router = new Router()
|
|
6
|
+
|
|
7
|
+
router
|
|
8
|
+
.put(
|
|
9
|
+
"/api/system/accounts/:accountId/metadata",
|
|
10
|
+
middleware.internalApi,
|
|
11
|
+
controller.save
|
|
12
|
+
)
|
|
13
|
+
.delete(
|
|
14
|
+
"/api/system/accounts/:accountId/metadata",
|
|
15
|
+
middleware.internalApi,
|
|
16
|
+
controller.destroy
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
export = router
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { accounts } from "../../../../sdk"
|
|
2
|
+
import { TestConfiguration, structures, API } from "../../../../tests"
|
|
3
|
+
import { v4 as uuid } from "uuid"
|
|
4
|
+
|
|
5
|
+
describe("accounts", () => {
|
|
6
|
+
const config = new TestConfiguration()
|
|
7
|
+
const api = new API(config)
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
await config.beforeAll()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
afterAll(async () => {
|
|
14
|
+
await config.afterAll()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe("metadata", () => {
|
|
22
|
+
describe("saveMetadata", () => {
|
|
23
|
+
it("saves account metadata", async () => {
|
|
24
|
+
let account = structures.accounts.account()
|
|
25
|
+
|
|
26
|
+
const response = await api.accounts.saveMetadata(account)
|
|
27
|
+
|
|
28
|
+
const id = accounts.formatAccountMetadataId(account.accountId)
|
|
29
|
+
const metadata = await accounts.getMetadata(id)
|
|
30
|
+
expect(response).toStrictEqual(metadata)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe("destroyMetadata", () => {
|
|
35
|
+
it("destroys account metadata", async () => {
|
|
36
|
+
const account = structures.accounts.account()
|
|
37
|
+
await api.accounts.saveMetadata(account)
|
|
38
|
+
|
|
39
|
+
await api.accounts.destroyMetadata(account.accountId)
|
|
40
|
+
|
|
41
|
+
const deleted = await accounts.getMetadata(account.accountId)
|
|
42
|
+
expect(deleted).toBe(undefined)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("destroys account metadata that does not exist", async () => {
|
|
46
|
+
const id = uuid()
|
|
47
|
+
|
|
48
|
+
const response = await api.accounts.destroyMetadata(id)
|
|
49
|
+
|
|
50
|
+
expect(response.status).toBe(404)
|
|
51
|
+
expect(response.body.message).toBe(
|
|
52
|
+
`id=${accounts.formatAccountMetadataId(id)} does not exist`
|
|
53
|
+
)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
})
|
|
@@ -20,13 +20,13 @@ if (!LOADED && isDev() && !isTest()) {
|
|
|
20
20
|
LOADED = true
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function parseIntSafe(number) {
|
|
23
|
+
function parseIntSafe(number: any) {
|
|
24
24
|
if (number) {
|
|
25
25
|
return parseInt(number)
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
const env = {
|
|
30
30
|
// auth
|
|
31
31
|
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
|
|
32
32
|
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
|
@@ -47,7 +47,7 @@ module.exports = {
|
|
|
47
47
|
CLUSTER_PORT: process.env.CLUSTER_PORT,
|
|
48
48
|
// flags
|
|
49
49
|
NODE_ENV: process.env.NODE_ENV,
|
|
50
|
-
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
|
50
|
+
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED || ""),
|
|
51
51
|
LOG_LEVEL: process.env.LOG_LEVEL,
|
|
52
52
|
MULTI_TENANCY: process.env.MULTI_TENANCY,
|
|
53
53
|
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
|
@@ -62,7 +62,7 @@ module.exports = {
|
|
|
62
62
|
// other
|
|
63
63
|
CHECKLIST_CACHE_TTL: parseIntSafe(process.env.CHECKLIST_CACHE_TTL) || 3600,
|
|
64
64
|
SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD,
|
|
65
|
-
_set(key, value) {
|
|
65
|
+
_set(key: any, value: any) {
|
|
66
66
|
process.env[key] = value
|
|
67
67
|
module.exports[key] = value
|
|
68
68
|
},
|
|
@@ -74,16 +74,17 @@ module.exports = {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// if some var haven't been set, define them
|
|
77
|
-
if (!
|
|
78
|
-
|
|
79
|
-
? "http://localhost:4001"
|
|
80
|
-
: "http://app-service:4002"
|
|
77
|
+
if (!env.APPS_URL) {
|
|
78
|
+
env.APPS_URL = isDev() ? "http://localhost:4001" : "http://app-service:4002"
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
// clean up any environment variable edge cases
|
|
84
82
|
for (let [key, value] of Object.entries(module.exports)) {
|
|
85
83
|
// handle the edge case of "0" to disable an environment variable
|
|
86
84
|
if (value === "0") {
|
|
87
|
-
|
|
85
|
+
// @ts-ignore
|
|
86
|
+
env[key] = 0
|
|
88
87
|
}
|
|
89
88
|
}
|
|
89
|
+
|
|
90
|
+
export = env
|
package/src/index.ts
CHANGED
|
@@ -71,9 +71,7 @@ server.on("close", async () => {
|
|
|
71
71
|
return
|
|
72
72
|
}
|
|
73
73
|
shuttingDown = true
|
|
74
|
-
|
|
75
|
-
console.log("Server Closed")
|
|
76
|
-
}
|
|
74
|
+
console.log("Server Closed")
|
|
77
75
|
await redis.shutdown()
|
|
78
76
|
await events.shutdown()
|
|
79
77
|
if (!env.isTest()) {
|
|
@@ -86,7 +84,7 @@ const shutdown = () => {
|
|
|
86
84
|
server.destroy()
|
|
87
85
|
}
|
|
88
86
|
|
|
89
|
-
|
|
87
|
+
export = server.listen(parseInt(env.PORT || 4002), async () => {
|
|
90
88
|
console.log(`Worker running on ${JSON.stringify(server.address())}`)
|
|
91
89
|
await redis.init()
|
|
92
90
|
})
|
|
@@ -100,3 +98,7 @@ process.on("uncaughtException", err => {
|
|
|
100
98
|
process.on("SIGTERM", () => {
|
|
101
99
|
shutdown()
|
|
102
100
|
})
|
|
101
|
+
|
|
102
|
+
process.on("SIGINT", () => {
|
|
103
|
+
shutdown()
|
|
104
|
+
})
|