@budibase/worker 2.3.18-alpha.2 → 2.3.18-alpha.21
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 +1 -1
- package/package.json +8 -7
- package/scripts/test.sh +12 -0
- package/src/api/controllers/global/auth.ts +38 -47
- package/src/api/controllers/global/configs.ts +150 -142
- package/src/api/controllers/global/users.ts +149 -10
- package/src/api/routes/global/configs.ts +6 -12
- package/src/api/routes/global/tests/auditLogs.spec.ts +111 -0
- package/src/api/routes/global/tests/auth.spec.ts +3 -3
- package/src/api/routes/global/tests/configs.spec.ts +88 -60
- package/src/api/routes/global/tests/realEmail.spec.ts +1 -0
- package/src/api/routes/global/tests/users.spec.ts +5 -1
- package/src/api/routes/global/users.ts +17 -12
- package/src/api/routes/index.ts +3 -0
- package/src/db/index.ts +8 -2
- package/src/environment.ts +1 -11
- package/src/index.ts +18 -2
- package/src/sdk/auth/auth.ts +2 -2
- package/src/sdk/users/tests/users.spec.ts +31 -7
- package/src/sdk/users/users.ts +31 -10
- package/src/tests/api/auditLogs.ts +26 -0
- package/src/tests/api/configs.ts +8 -0
- package/src/tests/api/email.ts +1 -0
- package/src/tests/api/index.ts +3 -0
- package/src/tests/structures/configs.ts +20 -13
- package/src/utilities/email.ts +19 -65
- package/src/utilities/redis.ts +39 -1
- package/src/utilities/templates.ts +4 -9
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
checkInviteCode,
|
|
3
|
+
getInviteCodes,
|
|
4
|
+
updateInviteCode,
|
|
5
|
+
} from "../../../utilities/redis"
|
|
6
|
+
// import sdk from "../../../sdk"
|
|
2
7
|
import * as userSdk from "../../../sdk/users"
|
|
3
8
|
import env from "../../../environment"
|
|
4
9
|
import {
|
|
@@ -28,6 +33,7 @@ import {
|
|
|
28
33
|
platform,
|
|
29
34
|
} from "@budibase/backend-core"
|
|
30
35
|
import { checkAnyUserExists } from "../../../utilities/users"
|
|
36
|
+
import { isEmailConfigured } from "../../../utilities/email"
|
|
31
37
|
|
|
32
38
|
const MAX_USERS_UPLOAD_LIMIT = 1000
|
|
33
39
|
|
|
@@ -179,16 +185,28 @@ export const destroy = async (ctx: any) => {
|
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
187
|
|
|
188
|
+
export const getAppUsers = async (ctx: any) => {
|
|
189
|
+
const body = ctx.request.body as SearchUsersRequest
|
|
190
|
+
const users = await userSdk.getUsersByAppAccess(body?.appId)
|
|
191
|
+
|
|
192
|
+
ctx.body = { data: users }
|
|
193
|
+
}
|
|
194
|
+
|
|
182
195
|
export const search = async (ctx: any) => {
|
|
183
196
|
const body = ctx.request.body as SearchUsersRequest
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
197
|
+
|
|
198
|
+
if (body.paginated === false) {
|
|
199
|
+
await getAppUsers(ctx)
|
|
200
|
+
} else {
|
|
201
|
+
const paginated = await userSdk.paginatedUsers(body)
|
|
202
|
+
// user hashed password shouldn't ever be returned
|
|
203
|
+
for (let user of paginated.data) {
|
|
204
|
+
if (user) {
|
|
205
|
+
delete user.password
|
|
206
|
+
}
|
|
189
207
|
}
|
|
208
|
+
ctx.body = paginated
|
|
190
209
|
}
|
|
191
|
-
ctx.body = paginated
|
|
192
210
|
}
|
|
193
211
|
|
|
194
212
|
// called internally by app server user fetch
|
|
@@ -218,9 +236,71 @@ export const tenantUserLookup = async (ctx: any) => {
|
|
|
218
236
|
}
|
|
219
237
|
}
|
|
220
238
|
|
|
239
|
+
/*
|
|
240
|
+
Encapsulate the app user onboarding flows here.
|
|
241
|
+
*/
|
|
242
|
+
export const onboardUsers = async (ctx: any) => {
|
|
243
|
+
const request = ctx.request.body as InviteUsersRequest | BulkUserRequest
|
|
244
|
+
const isBulkCreate = "create" in request
|
|
245
|
+
|
|
246
|
+
const emailConfigured = await isEmailConfigured()
|
|
247
|
+
|
|
248
|
+
let onboardingResponse
|
|
249
|
+
|
|
250
|
+
if (isBulkCreate) {
|
|
251
|
+
// @ts-ignore
|
|
252
|
+
const { users, groups, roles } = request.create
|
|
253
|
+
const assignUsers = users.map((user: User) => (user.roles = roles))
|
|
254
|
+
onboardingResponse = await userSdk.bulkCreate(assignUsers, groups)
|
|
255
|
+
ctx.body = onboardingResponse
|
|
256
|
+
} else if (emailConfigured) {
|
|
257
|
+
onboardingResponse = await inviteMultiple(ctx)
|
|
258
|
+
} else if (!emailConfigured) {
|
|
259
|
+
const inviteRequest = ctx.request.body as InviteUsersRequest
|
|
260
|
+
|
|
261
|
+
let createdPasswords: any = {}
|
|
262
|
+
|
|
263
|
+
const users: User[] = inviteRequest.map(invite => {
|
|
264
|
+
let password = Math.random().toString(36).substring(2, 22)
|
|
265
|
+
|
|
266
|
+
// Temp password to be passed to the user.
|
|
267
|
+
createdPasswords[invite.email] = password
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
email: invite.email,
|
|
271
|
+
password,
|
|
272
|
+
forceResetPassword: true,
|
|
273
|
+
roles: invite.userInfo.apps,
|
|
274
|
+
admin: { global: false },
|
|
275
|
+
builder: { global: false },
|
|
276
|
+
tenantId: tenancy.getTenantId(),
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
let bulkCreateReponse = await userSdk.bulkCreate(users, [])
|
|
280
|
+
|
|
281
|
+
// Apply temporary credentials
|
|
282
|
+
let createWithCredentials = {
|
|
283
|
+
...bulkCreateReponse,
|
|
284
|
+
successful: bulkCreateReponse?.successful.map(user => {
|
|
285
|
+
return {
|
|
286
|
+
...user,
|
|
287
|
+
password: createdPasswords[user.email],
|
|
288
|
+
}
|
|
289
|
+
}),
|
|
290
|
+
created: true,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
ctx.body = createWithCredentials
|
|
294
|
+
} else {
|
|
295
|
+
ctx.throw(400, "User onboarding failed")
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
221
299
|
export const invite = async (ctx: any) => {
|
|
222
300
|
const request = ctx.request.body as InviteUserRequest
|
|
223
|
-
|
|
301
|
+
|
|
302
|
+
let multiRequest = [request] as InviteUsersRequest
|
|
303
|
+
const response = await userSdk.invite(multiRequest)
|
|
224
304
|
|
|
225
305
|
// explicitly throw for single user invite
|
|
226
306
|
if (response.unsuccessful.length) {
|
|
@@ -234,6 +314,8 @@ export const invite = async (ctx: any) => {
|
|
|
234
314
|
|
|
235
315
|
ctx.body = {
|
|
236
316
|
message: "Invitation has been sent.",
|
|
317
|
+
successful: response.successful,
|
|
318
|
+
unsuccessful: response.unsuccessful,
|
|
237
319
|
}
|
|
238
320
|
}
|
|
239
321
|
|
|
@@ -255,6 +337,53 @@ export const checkInvite = async (ctx: any) => {
|
|
|
255
337
|
}
|
|
256
338
|
}
|
|
257
339
|
|
|
340
|
+
export const getUserInvites = async (ctx: any) => {
|
|
341
|
+
let invites
|
|
342
|
+
try {
|
|
343
|
+
// Restricted to the currently authenticated tenant
|
|
344
|
+
invites = await getInviteCodes([ctx.user.tenantId])
|
|
345
|
+
} catch (e) {
|
|
346
|
+
ctx.throw(400, "There was a problem fetching invites")
|
|
347
|
+
}
|
|
348
|
+
ctx.body = invites
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export const updateInvite = async (ctx: any) => {
|
|
352
|
+
const { code } = ctx.params
|
|
353
|
+
let updateBody = { ...ctx.request.body }
|
|
354
|
+
|
|
355
|
+
delete updateBody.email
|
|
356
|
+
|
|
357
|
+
let invite
|
|
358
|
+
try {
|
|
359
|
+
invite = await checkInviteCode(code, false)
|
|
360
|
+
if (!invite) {
|
|
361
|
+
throw new Error("The invite could not be retrieved")
|
|
362
|
+
}
|
|
363
|
+
} catch (e) {
|
|
364
|
+
ctx.throw(400, "There was a problem with the invite")
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let updated = {
|
|
368
|
+
...invite,
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (!updateBody?.apps || !Object.keys(updateBody?.apps).length) {
|
|
372
|
+
updated.info.apps = []
|
|
373
|
+
} else {
|
|
374
|
+
updated.info = {
|
|
375
|
+
...invite.info,
|
|
376
|
+
apps: {
|
|
377
|
+
...invite.info.apps,
|
|
378
|
+
...updateBody.apps,
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
await updateInviteCode(code, updated)
|
|
384
|
+
ctx.body = { ...invite }
|
|
385
|
+
}
|
|
386
|
+
|
|
258
387
|
export const inviteAccept = async (
|
|
259
388
|
ctx: Ctx<AcceptUserInviteRequest, AcceptUserInviteResponse>
|
|
260
389
|
) => {
|
|
@@ -263,13 +392,23 @@ export const inviteAccept = async (
|
|
|
263
392
|
// info is an extension of the user object that was stored by global
|
|
264
393
|
const { email, info }: any = await checkInviteCode(inviteCode)
|
|
265
394
|
const user = await tenancy.doInTenant(info.tenantId, async () => {
|
|
266
|
-
|
|
395
|
+
let request = {
|
|
267
396
|
firstName,
|
|
268
397
|
lastName,
|
|
269
398
|
password,
|
|
270
399
|
email,
|
|
400
|
+
roles: info.apps,
|
|
401
|
+
tenantId: info.tenantId,
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
delete info.apps
|
|
405
|
+
|
|
406
|
+
request = {
|
|
407
|
+
...request,
|
|
271
408
|
...info,
|
|
272
|
-
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const saved = await userSdk.save(request)
|
|
273
412
|
const db = tenancy.getGlobalDB()
|
|
274
413
|
const user = await db.get(saved._id)
|
|
275
414
|
await events.user.inviteAccepted(user)
|
|
@@ -34,8 +34,8 @@ function settingValidation() {
|
|
|
34
34
|
function googleValidation() {
|
|
35
35
|
// prettier-ignore
|
|
36
36
|
return Joi.object({
|
|
37
|
-
clientID: Joi.
|
|
38
|
-
clientSecret: Joi.
|
|
37
|
+
clientID: Joi.string().required(),
|
|
38
|
+
clientSecret: Joi.string().required(),
|
|
39
39
|
activated: Joi.boolean().required(),
|
|
40
40
|
}).unknown(true)
|
|
41
41
|
}
|
|
@@ -45,12 +45,12 @@ function oidcValidation() {
|
|
|
45
45
|
return Joi.object({
|
|
46
46
|
configs: Joi.array().items(
|
|
47
47
|
Joi.object({
|
|
48
|
-
clientID: Joi.
|
|
49
|
-
clientSecret: Joi.
|
|
50
|
-
configUrl: Joi.
|
|
48
|
+
clientID: Joi.string().required(),
|
|
49
|
+
clientSecret: Joi.string().required(),
|
|
50
|
+
configUrl: Joi.string().required(),
|
|
51
51
|
logo: Joi.string().allow("", null),
|
|
52
52
|
name: Joi.string().allow("", null),
|
|
53
|
-
uuid: Joi.
|
|
53
|
+
uuid: Joi.string().required(),
|
|
54
54
|
activated: Joi.boolean().required(),
|
|
55
55
|
scopes: Joi.array().optional()
|
|
56
56
|
})
|
|
@@ -104,13 +104,7 @@ router
|
|
|
104
104
|
controller.save
|
|
105
105
|
)
|
|
106
106
|
.delete("/api/global/configs/:id/:rev", auth.adminOnly, controller.destroy)
|
|
107
|
-
.get("/api/global/configs", controller.fetch)
|
|
108
107
|
.get("/api/global/configs/checklist", controller.configChecklist)
|
|
109
|
-
.get(
|
|
110
|
-
"/api/global/configs/all/:type",
|
|
111
|
-
buildConfigGetValidation(),
|
|
112
|
-
controller.fetch
|
|
113
|
-
)
|
|
114
108
|
.get("/api/global/configs/public", controller.publicSettings)
|
|
115
109
|
.get("/api/global/configs/public/oidc", controller.publicOidc)
|
|
116
110
|
.get("/api/global/configs/:type", buildConfigGetValidation(), controller.find)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { mocks, structures } from "@budibase/backend-core/tests"
|
|
2
|
+
import { context, events } from "@budibase/backend-core"
|
|
3
|
+
import { Event, IdentityType } from "@budibase/types"
|
|
4
|
+
import { TestConfiguration } from "../../../../tests"
|
|
5
|
+
|
|
6
|
+
mocks.licenses.useAuditLogs()
|
|
7
|
+
|
|
8
|
+
const BASE_IDENTITY = {
|
|
9
|
+
account: undefined,
|
|
10
|
+
type: IdentityType.USER,
|
|
11
|
+
}
|
|
12
|
+
const USER_AUDIT_LOG_COUNT = 3
|
|
13
|
+
const APP_ID = "app_1"
|
|
14
|
+
|
|
15
|
+
describe("/api/global/auditlogs", () => {
|
|
16
|
+
const config = new TestConfiguration()
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
await config.beforeAll()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await config.afterAll()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe("POST /api/global/auditlogs/search", () => {
|
|
27
|
+
it("should be able to fire some events (create audit logs)", async () => {
|
|
28
|
+
await context.doInTenant(config.tenantId, async () => {
|
|
29
|
+
const userId = config.user!._id!
|
|
30
|
+
const identity = {
|
|
31
|
+
...BASE_IDENTITY,
|
|
32
|
+
_id: userId,
|
|
33
|
+
tenantId: config.tenantId,
|
|
34
|
+
}
|
|
35
|
+
await context.doInIdentityContext(identity, async () => {
|
|
36
|
+
for (let i = 0; i < USER_AUDIT_LOG_COUNT; i++) {
|
|
37
|
+
await events.user.created(structures.users.user())
|
|
38
|
+
}
|
|
39
|
+
await context.doInAppContext(APP_ID, async () => {
|
|
40
|
+
await events.app.created(structures.apps.app(APP_ID))
|
|
41
|
+
})
|
|
42
|
+
// fetch the user created events
|
|
43
|
+
const response = await config.api.auditLogs.search({
|
|
44
|
+
events: [Event.USER_CREATED],
|
|
45
|
+
})
|
|
46
|
+
expect(response.data).toBeDefined()
|
|
47
|
+
// there will be an initial event which comes from the default user creation
|
|
48
|
+
expect(response.data.length).toBe(USER_AUDIT_LOG_COUNT + 1)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("should be able to search by event", async () => {
|
|
54
|
+
const response = await config.api.auditLogs.search({
|
|
55
|
+
events: [Event.USER_CREATED],
|
|
56
|
+
})
|
|
57
|
+
expect(response.data.length).toBeGreaterThan(0)
|
|
58
|
+
for (let log of response.data) {
|
|
59
|
+
expect(log.event).toBe(Event.USER_CREATED)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("should be able to search by time range (frozen)", async () => {
|
|
64
|
+
// this is frozen, only need to add 1 and minus 1
|
|
65
|
+
const now = new Date()
|
|
66
|
+
const start = new Date()
|
|
67
|
+
start.setSeconds(now.getSeconds() - 1)
|
|
68
|
+
const end = new Date()
|
|
69
|
+
end.setSeconds(now.getSeconds() + 1)
|
|
70
|
+
const response = await config.api.auditLogs.search({
|
|
71
|
+
startDate: start.toISOString(),
|
|
72
|
+
endDate: end.toISOString(),
|
|
73
|
+
})
|
|
74
|
+
expect(response.data.length).toBeGreaterThan(0)
|
|
75
|
+
for (let log of response.data) {
|
|
76
|
+
expect(log.timestamp).toBe(now.toISOString())
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("should be able to search by user ID", async () => {
|
|
81
|
+
const userId = config.user!._id!
|
|
82
|
+
const response = await config.api.auditLogs.search({
|
|
83
|
+
userIds: [userId],
|
|
84
|
+
})
|
|
85
|
+
expect(response.data.length).toBeGreaterThan(0)
|
|
86
|
+
for (let log of response.data) {
|
|
87
|
+
expect(log.user._id).toBe(userId)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("should be able to search by app ID", async () => {
|
|
92
|
+
const response = await config.api.auditLogs.search({
|
|
93
|
+
appIds: [APP_ID],
|
|
94
|
+
})
|
|
95
|
+
expect(response.data.length).toBeGreaterThan(0)
|
|
96
|
+
for (let log of response.data) {
|
|
97
|
+
expect(log.app?._id).toBe(APP_ID)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it("should be able to search by full string", async () => {
|
|
102
|
+
const response = await config.api.auditLogs.search({
|
|
103
|
+
fullSearch: "User",
|
|
104
|
+
})
|
|
105
|
+
expect(response.data.length).toBeGreaterThan(0)
|
|
106
|
+
for (let log of response.data) {
|
|
107
|
+
expect(log.name.includes("User")).toBe(true)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
})
|
|
@@ -110,7 +110,7 @@ describe("/api/global/auth", () => {
|
|
|
110
110
|
)
|
|
111
111
|
|
|
112
112
|
expect(response.body).toEqual({
|
|
113
|
-
message: "
|
|
113
|
+
message: "Password login is disabled for this user",
|
|
114
114
|
status: 400,
|
|
115
115
|
})
|
|
116
116
|
}
|
|
@@ -175,7 +175,7 @@ describe("/api/global/auth", () => {
|
|
|
175
175
|
)
|
|
176
176
|
|
|
177
177
|
expect(res.body).toEqual({
|
|
178
|
-
message: "
|
|
178
|
+
message: "Password reset is disabled for this user",
|
|
179
179
|
status: 400,
|
|
180
180
|
error: {
|
|
181
181
|
code: "http",
|
|
@@ -367,7 +367,7 @@ describe("/api/global/auth", () => {
|
|
|
367
367
|
|
|
368
368
|
const res = await config.api.configs.OIDCCallback(configId, preAuthRes)
|
|
369
369
|
|
|
370
|
-
expect(events.auth.login).toBeCalledWith("oidc")
|
|
370
|
+
expect(events.auth.login).toBeCalledWith("oidc", "oauth@example.com")
|
|
371
371
|
expect(events.auth.login).toBeCalledTimes(1)
|
|
372
372
|
expect(res.status).toBe(302)
|
|
373
373
|
const location: string = res.get("location")
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
jest.mock("nodemailer")
|
|
3
3
|
import { TestConfiguration, structures, mocks } from "../../../../tests"
|
|
4
4
|
mocks.email.mock()
|
|
5
|
-
import {
|
|
5
|
+
import { events } from "@budibase/backend-core"
|
|
6
|
+
import { GetPublicSettingsResponse, Config, ConfigType } from "@budibase/types"
|
|
6
7
|
|
|
7
8
|
describe("configs", () => {
|
|
8
9
|
const config = new TestConfiguration()
|
|
@@ -19,22 +20,29 @@ describe("configs", () => {
|
|
|
19
20
|
await config.afterAll()
|
|
20
21
|
})
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
_rev,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const res = await config.api.configs.saveConfig(data)
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
...data,
|
|
34
|
-
...res.body,
|
|
35
|
-
}
|
|
23
|
+
const saveConfig = async (conf: Config, _id?: string, _rev?: string) => {
|
|
24
|
+
const data = {
|
|
25
|
+
...conf,
|
|
26
|
+
_id,
|
|
27
|
+
_rev,
|
|
36
28
|
}
|
|
37
|
-
|
|
29
|
+
const res = await config.api.configs.saveConfig(data)
|
|
30
|
+
return {
|
|
31
|
+
...data,
|
|
32
|
+
...res.body,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const saveSettingsConfig = async (
|
|
37
|
+
conf?: any,
|
|
38
|
+
_id?: string,
|
|
39
|
+
_rev?: string
|
|
40
|
+
) => {
|
|
41
|
+
const settingsConfig = structures.configs.settings(conf)
|
|
42
|
+
return saveConfig(settingsConfig, _id, _rev)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("POST /api/global/configs", () => {
|
|
38
46
|
describe("google", () => {
|
|
39
47
|
const saveGoogleConfig = async (
|
|
40
48
|
conf?: any,
|
|
@@ -49,20 +57,20 @@ describe("configs", () => {
|
|
|
49
57
|
it("should create activated google config", async () => {
|
|
50
58
|
await saveGoogleConfig()
|
|
51
59
|
expect(events.auth.SSOCreated).toBeCalledTimes(1)
|
|
52
|
-
expect(events.auth.SSOCreated).toBeCalledWith(
|
|
60
|
+
expect(events.auth.SSOCreated).toBeCalledWith(ConfigType.GOOGLE)
|
|
53
61
|
expect(events.auth.SSODeactivated).not.toBeCalled()
|
|
54
62
|
expect(events.auth.SSOActivated).toBeCalledTimes(1)
|
|
55
|
-
expect(events.auth.SSOActivated).toBeCalledWith(
|
|
56
|
-
await config.deleteConfig(
|
|
63
|
+
expect(events.auth.SSOActivated).toBeCalledWith(ConfigType.GOOGLE)
|
|
64
|
+
await config.deleteConfig(ConfigType.GOOGLE)
|
|
57
65
|
})
|
|
58
66
|
|
|
59
67
|
it("should create deactivated google config", async () => {
|
|
60
68
|
await saveGoogleConfig({ activated: false })
|
|
61
69
|
expect(events.auth.SSOCreated).toBeCalledTimes(1)
|
|
62
|
-
expect(events.auth.SSOCreated).toBeCalledWith(
|
|
70
|
+
expect(events.auth.SSOCreated).toBeCalledWith(ConfigType.GOOGLE)
|
|
63
71
|
expect(events.auth.SSOActivated).not.toBeCalled()
|
|
64
72
|
expect(events.auth.SSODeactivated).not.toBeCalled()
|
|
65
|
-
await config.deleteConfig(
|
|
73
|
+
await config.deleteConfig(ConfigType.GOOGLE)
|
|
66
74
|
})
|
|
67
75
|
})
|
|
68
76
|
|
|
@@ -76,11 +84,11 @@ describe("configs", () => {
|
|
|
76
84
|
googleConf._rev
|
|
77
85
|
)
|
|
78
86
|
expect(events.auth.SSOUpdated).toBeCalledTimes(1)
|
|
79
|
-
expect(events.auth.SSOUpdated).toBeCalledWith(
|
|
87
|
+
expect(events.auth.SSOUpdated).toBeCalledWith(ConfigType.GOOGLE)
|
|
80
88
|
expect(events.auth.SSOActivated).not.toBeCalled()
|
|
81
89
|
expect(events.auth.SSODeactivated).toBeCalledTimes(1)
|
|
82
|
-
expect(events.auth.SSODeactivated).toBeCalledWith(
|
|
83
|
-
await config.deleteConfig(
|
|
90
|
+
expect(events.auth.SSODeactivated).toBeCalledWith(ConfigType.GOOGLE)
|
|
91
|
+
await config.deleteConfig(ConfigType.GOOGLE)
|
|
84
92
|
})
|
|
85
93
|
|
|
86
94
|
it("should update google config to activated", async () => {
|
|
@@ -92,11 +100,11 @@ describe("configs", () => {
|
|
|
92
100
|
googleConf._rev
|
|
93
101
|
)
|
|
94
102
|
expect(events.auth.SSOUpdated).toBeCalledTimes(1)
|
|
95
|
-
expect(events.auth.SSOUpdated).toBeCalledWith(
|
|
103
|
+
expect(events.auth.SSOUpdated).toBeCalledWith(ConfigType.GOOGLE)
|
|
96
104
|
expect(events.auth.SSODeactivated).not.toBeCalled()
|
|
97
105
|
expect(events.auth.SSOActivated).toBeCalledTimes(1)
|
|
98
|
-
expect(events.auth.SSOActivated).toBeCalledWith(
|
|
99
|
-
await config.deleteConfig(
|
|
106
|
+
expect(events.auth.SSOActivated).toBeCalledWith(ConfigType.GOOGLE)
|
|
107
|
+
await config.deleteConfig(ConfigType.GOOGLE)
|
|
100
108
|
})
|
|
101
109
|
})
|
|
102
110
|
})
|
|
@@ -115,20 +123,20 @@ describe("configs", () => {
|
|
|
115
123
|
it("should create activated OIDC config", async () => {
|
|
116
124
|
await saveOIDCConfig()
|
|
117
125
|
expect(events.auth.SSOCreated).toBeCalledTimes(1)
|
|
118
|
-
expect(events.auth.SSOCreated).toBeCalledWith(
|
|
126
|
+
expect(events.auth.SSOCreated).toBeCalledWith(ConfigType.OIDC)
|
|
119
127
|
expect(events.auth.SSODeactivated).not.toBeCalled()
|
|
120
128
|
expect(events.auth.SSOActivated).toBeCalledTimes(1)
|
|
121
|
-
expect(events.auth.SSOActivated).toBeCalledWith(
|
|
122
|
-
await config.deleteConfig(
|
|
129
|
+
expect(events.auth.SSOActivated).toBeCalledWith(ConfigType.OIDC)
|
|
130
|
+
await config.deleteConfig(ConfigType.OIDC)
|
|
123
131
|
})
|
|
124
132
|
|
|
125
133
|
it("should create deactivated OIDC config", async () => {
|
|
126
134
|
await saveOIDCConfig({ activated: false })
|
|
127
135
|
expect(events.auth.SSOCreated).toBeCalledTimes(1)
|
|
128
|
-
expect(events.auth.SSOCreated).toBeCalledWith(
|
|
136
|
+
expect(events.auth.SSOCreated).toBeCalledWith(ConfigType.OIDC)
|
|
129
137
|
expect(events.auth.SSOActivated).not.toBeCalled()
|
|
130
138
|
expect(events.auth.SSODeactivated).not.toBeCalled()
|
|
131
|
-
await config.deleteConfig(
|
|
139
|
+
await config.deleteConfig(ConfigType.OIDC)
|
|
132
140
|
})
|
|
133
141
|
})
|
|
134
142
|
|
|
@@ -142,11 +150,11 @@ describe("configs", () => {
|
|
|
142
150
|
oidcConf._rev
|
|
143
151
|
)
|
|
144
152
|
expect(events.auth.SSOUpdated).toBeCalledTimes(1)
|
|
145
|
-
expect(events.auth.SSOUpdated).toBeCalledWith(
|
|
153
|
+
expect(events.auth.SSOUpdated).toBeCalledWith(ConfigType.OIDC)
|
|
146
154
|
expect(events.auth.SSOActivated).not.toBeCalled()
|
|
147
155
|
expect(events.auth.SSODeactivated).toBeCalledTimes(1)
|
|
148
|
-
expect(events.auth.SSODeactivated).toBeCalledWith(
|
|
149
|
-
await config.deleteConfig(
|
|
156
|
+
expect(events.auth.SSODeactivated).toBeCalledWith(ConfigType.OIDC)
|
|
157
|
+
await config.deleteConfig(ConfigType.OIDC)
|
|
150
158
|
})
|
|
151
159
|
|
|
152
160
|
it("should update OIDC config to activated", async () => {
|
|
@@ -158,11 +166,11 @@ describe("configs", () => {
|
|
|
158
166
|
oidcConf._rev
|
|
159
167
|
)
|
|
160
168
|
expect(events.auth.SSOUpdated).toBeCalledTimes(1)
|
|
161
|
-
expect(events.auth.SSOUpdated).toBeCalledWith(
|
|
169
|
+
expect(events.auth.SSOUpdated).toBeCalledWith(ConfigType.OIDC)
|
|
162
170
|
expect(events.auth.SSODeactivated).not.toBeCalled()
|
|
163
171
|
expect(events.auth.SSOActivated).toBeCalledTimes(1)
|
|
164
|
-
expect(events.auth.SSOActivated).toBeCalledWith(
|
|
165
|
-
await config.deleteConfig(
|
|
172
|
+
expect(events.auth.SSOActivated).toBeCalledWith(ConfigType.OIDC)
|
|
173
|
+
await config.deleteConfig(ConfigType.OIDC)
|
|
166
174
|
})
|
|
167
175
|
})
|
|
168
176
|
})
|
|
@@ -179,11 +187,11 @@ describe("configs", () => {
|
|
|
179
187
|
|
|
180
188
|
describe("create", () => {
|
|
181
189
|
it("should create SMTP config", async () => {
|
|
182
|
-
await config.deleteConfig(
|
|
190
|
+
await config.deleteConfig(ConfigType.SMTP)
|
|
183
191
|
await saveSMTPConfig()
|
|
184
192
|
expect(events.email.SMTPUpdated).not.toBeCalled()
|
|
185
193
|
expect(events.email.SMTPCreated).toBeCalledTimes(1)
|
|
186
|
-
await config.deleteConfig(
|
|
194
|
+
await config.deleteConfig(ConfigType.SMTP)
|
|
187
195
|
})
|
|
188
196
|
})
|
|
189
197
|
|
|
@@ -194,24 +202,15 @@ describe("configs", () => {
|
|
|
194
202
|
await saveSMTPConfig(smtpConf.config, smtpConf._id, smtpConf._rev)
|
|
195
203
|
expect(events.email.SMTPCreated).not.toBeCalled()
|
|
196
204
|
expect(events.email.SMTPUpdated).toBeCalledTimes(1)
|
|
197
|
-
await config.deleteConfig(
|
|
205
|
+
await config.deleteConfig(ConfigType.SMTP)
|
|
198
206
|
})
|
|
199
207
|
})
|
|
200
208
|
})
|
|
201
209
|
|
|
202
210
|
describe("settings", () => {
|
|
203
|
-
const saveSettingsConfig = async (
|
|
204
|
-
conf?: any,
|
|
205
|
-
_id?: string,
|
|
206
|
-
_rev?: string
|
|
207
|
-
) => {
|
|
208
|
-
const settingsConfig = structures.configs.settings(conf)
|
|
209
|
-
return saveConfig(settingsConfig, _id, _rev)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
211
|
describe("create", () => {
|
|
213
212
|
it("should create settings config with default settings", async () => {
|
|
214
|
-
await config.deleteConfig(
|
|
213
|
+
await config.deleteConfig(ConfigType.SETTINGS)
|
|
215
214
|
|
|
216
215
|
await saveSettingsConfig()
|
|
217
216
|
|
|
@@ -222,7 +221,7 @@ describe("configs", () => {
|
|
|
222
221
|
|
|
223
222
|
it("should create settings config with non-default settings", async () => {
|
|
224
223
|
config.selfHosted()
|
|
225
|
-
await config.deleteConfig(
|
|
224
|
+
await config.deleteConfig(ConfigType.SETTINGS)
|
|
226
225
|
const conf = {
|
|
227
226
|
company: "acme",
|
|
228
227
|
logoUrl: "http://example.com",
|
|
@@ -241,7 +240,7 @@ describe("configs", () => {
|
|
|
241
240
|
describe("update", () => {
|
|
242
241
|
it("should update settings config", async () => {
|
|
243
242
|
config.selfHosted()
|
|
244
|
-
await config.deleteConfig(
|
|
243
|
+
await config.deleteConfig(ConfigType.SETTINGS)
|
|
245
244
|
const settingsConfig = await saveSettingsConfig()
|
|
246
245
|
settingsConfig.config.company = "acme"
|
|
247
246
|
settingsConfig.config.logoUrl = "http://example.com"
|
|
@@ -262,14 +261,43 @@ describe("configs", () => {
|
|
|
262
261
|
})
|
|
263
262
|
})
|
|
264
263
|
|
|
265
|
-
|
|
266
|
-
|
|
264
|
+
describe("GET /api/global/configs/checklist", () => {
|
|
265
|
+
it("should return the correct checklist", async () => {
|
|
266
|
+
await config.saveSmtpConfig()
|
|
267
|
+
|
|
268
|
+
const res = await config.api.configs.getConfigChecklist()
|
|
269
|
+
const checklist = res.body
|
|
267
270
|
|
|
268
|
-
|
|
269
|
-
|
|
271
|
+
expect(checklist.apps.checked).toBeFalsy()
|
|
272
|
+
expect(checklist.smtp.checked).toBeTruthy()
|
|
273
|
+
expect(checklist.adminUser.checked).toBeTruthy()
|
|
274
|
+
})
|
|
275
|
+
})
|
|
270
276
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
277
|
+
describe("GET /api/global/configs/public", () => {
|
|
278
|
+
it("should return the expected public settings", async () => {
|
|
279
|
+
await saveSettingsConfig()
|
|
280
|
+
|
|
281
|
+
const res = await config.api.configs.getPublicSettings()
|
|
282
|
+
const body = res.body as GetPublicSettingsResponse
|
|
283
|
+
|
|
284
|
+
const expected = {
|
|
285
|
+
_id: "config_settings",
|
|
286
|
+
type: "settings",
|
|
287
|
+
config: {
|
|
288
|
+
company: "Budibase",
|
|
289
|
+
logoUrl: "",
|
|
290
|
+
analyticsEnabled: false,
|
|
291
|
+
google: true,
|
|
292
|
+
googleCallbackUrl: `http://localhost:10000/api/global/auth/${config.tenantId}/google/callback`,
|
|
293
|
+
isSSOEnforced: false,
|
|
294
|
+
oidc: false,
|
|
295
|
+
oidcCallbackUrl: `http://localhost:10000/api/global/auth/${config.tenantId}/oidc/callback`,
|
|
296
|
+
platformUrl: "http://localhost:10000",
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
delete body._rev
|
|
300
|
+
expect(body).toEqual(expected)
|
|
301
|
+
})
|
|
274
302
|
})
|
|
275
303
|
})
|
|
@@ -30,7 +30,11 @@ describe("/api/global/users", () => {
|
|
|
30
30
|
email
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
expect(res.body).
|
|
33
|
+
expect(res.body?.message).toBe("Invitation has been sent.")
|
|
34
|
+
expect(res.body?.unsuccessful.length).toBe(0)
|
|
35
|
+
expect(res.body?.successful.length).toBe(1)
|
|
36
|
+
expect(res.body?.successful[0].email).toBe(email)
|
|
37
|
+
|
|
34
38
|
expect(sendMailMock).toHaveBeenCalled()
|
|
35
39
|
expect(code).toBeDefined()
|
|
36
40
|
expect(events.user.invited).toBeCalledTimes(1)
|