@budibase/worker 2.5.9 → 2.5.10-alpha.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.
@@ -48,7 +48,7 @@ describe("/api/global/users", () => {
48
48
  400
49
49
  )
50
50
 
51
- expect(res.body.message).toBe("Unavailable")
51
+ expect(res.body.message).toBe(`Unavailable`)
52
52
  expect(sendMailMock).toHaveBeenCalledTimes(0)
53
53
  expect(code).toBeUndefined()
54
54
  expect(events.user.invited).toBeCalledTimes(0)
@@ -225,7 +225,9 @@ describe("/api/global/users", () => {
225
225
 
226
226
  const response = await config.api.users.saveUser(user, 400)
227
227
 
228
- expect(response.body.message).toBe(`Unavailable`)
228
+ expect(response.body.message).toBe(
229
+ `Email already in use: '${user.email}'`
230
+ )
229
231
  expect(events.user.created).toBeCalledTimes(0)
230
232
  })
231
233
 
@@ -237,7 +239,9 @@ describe("/api/global/users", () => {
237
239
  delete user._id
238
240
  const response = await config.api.users.saveUser(user, 400)
239
241
 
240
- expect(response.body.message).toBe(`Unavailable`)
242
+ expect(response.body.message).toBe(
243
+ `Email already in use: '${user.email}'`
244
+ )
241
245
  expect(events.user.created).toBeCalledTimes(0)
242
246
  })
243
247
  })
@@ -249,7 +253,9 @@ describe("/api/global/users", () => {
249
253
 
250
254
  const response = await config.api.users.saveUser(user, 400)
251
255
 
252
- expect(response.body.message).toBe(`Unavailable`)
256
+ expect(response.body.message).toBe(
257
+ `Email already in use: '${user.email}'`
258
+ )
253
259
  expect(events.user.created).toBeCalledTimes(0)
254
260
  })
255
261
 
@@ -1,5 +1,5 @@
1
1
  import Router from "@koa/router"
2
- import { api } from "@budibase/pro"
2
+ import { api as pro } from "@budibase/pro"
3
3
  import userRoutes from "./global/users"
4
4
  import configRoutes from "./global/configs"
5
5
  import workspaceRoutes from "./global/workspaces"
@@ -17,9 +17,6 @@ import migrationRoutes from "./system/migrations"
17
17
  import accountRoutes from "./system/accounts"
18
18
  import restoreRoutes from "./system/restore"
19
19
 
20
- let userGroupRoutes = api.groups
21
- let auditLogRoutes = api.auditLogs
22
-
23
20
  export const routes: Router[] = [
24
21
  configRoutes,
25
22
  userRoutes,
@@ -33,10 +30,11 @@ export const routes: Router[] = [
33
30
  statusRoutes,
34
31
  selfRoutes,
35
32
  licenseRoutes,
36
- userGroupRoutes,
37
- auditLogRoutes,
33
+ pro.groups,
34
+ pro.auditLogs,
38
35
  migrationRoutes,
39
36
  accountRoutes,
40
37
  restoreRoutes,
41
38
  eventRoutes,
39
+ pro.scim,
42
40
  ]
@@ -16,7 +16,8 @@
16
16
  cellspacing="0"
17
17
  >
18
18
  <img
19
- width="32px"
19
+ width="32"
20
+ height="32"
20
21
  style="margin-right:16px; vertical-align: middle;"
21
22
  alt="Budibase Logo"
22
23
  src="https://i.imgur.com/Xhdt1YP.png"
@@ -47,7 +47,6 @@ const environment = {
47
47
  // flags
48
48
  NODE_ENV: process.env.NODE_ENV,
49
49
  SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED || ""),
50
- LOG_LEVEL: process.env.LOG_LEVEL,
51
50
  MULTI_TENANCY: process.env.MULTI_TENANCY,
52
51
  DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
53
52
  SMTP_FALLBACK_ENABLED: process.env.SMTP_FALLBACK_ENABLED,
package/src/index.ts CHANGED
@@ -2,10 +2,6 @@ if (process.env.DD_APM_ENABLED) {
2
2
  require("./ddApm")
3
3
  }
4
4
 
5
- if (process.env.ELASTIC_APM_ENABLED) {
6
- require("./elasticApm")
7
- }
8
-
9
5
  // need to load environment first
10
6
  import env from "./environment"
11
7
  import { Scope } from "@sentry/node"
@@ -31,10 +27,11 @@ import api from "./api"
31
27
  import * as redis from "./utilities/redis"
32
28
  const Sentry = require("@sentry/node")
33
29
  const koaSession = require("koa-session")
34
- const logger = require("koa-pino-logger")
35
30
  const { userAgent } = require("koa-useragent")
36
31
 
37
32
  import destroyable from "server-destroy"
33
+ import { initPro } from "./initPro"
34
+ import { handleScimBody } from "./middleware/handleScimBody"
38
35
 
39
36
  // configure events to use the pro audit log write
40
37
  // can't integrate directly into backend-core due to cyclic issues
@@ -54,10 +51,12 @@ const app: Application = new Koa()
54
51
  app.keys = ["secret", "key"]
55
52
 
56
53
  // set up top level koa middleware
54
+ app.use(handleScimBody)
57
55
  app.use(koaBody({ multipart: true }))
56
+
58
57
  app.use(koaSession(app))
59
- app.use(middleware.logging)
60
- app.use(logger(logging.pinoSettings()))
58
+ app.use(middleware.correlation)
59
+ app.use(middleware.pino)
61
60
  app.use(userAgent)
62
61
 
63
62
  // authentication
@@ -108,6 +107,7 @@ const shutdown = () => {
108
107
 
109
108
  export default server.listen(parseInt(env.PORT || "4002"), async () => {
110
109
  console.log(`Worker running on ${JSON.stringify(server.address())}`)
110
+ await initPro()
111
111
  await redis.init()
112
112
  })
113
113
 
package/src/initPro.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { sdk as proSdk } from "@budibase/pro"
2
+ import * as userSdk from "./sdk/users"
3
+
4
+ export const initPro = async () => {
5
+ await proSdk.init({
6
+ scimUserServiceConfig: {
7
+ functions: {
8
+ saveUser: userSdk.save,
9
+ removeUser: (id: string) => userSdk.destroy(id),
10
+ },
11
+ },
12
+ })
13
+ }
@@ -0,0 +1,12 @@
1
+ import { Ctx } from "@budibase/types"
2
+
3
+ export const handleScimBody = (ctx: Ctx, next: any) => {
4
+ var type = ctx.req.headers["content-type"] || ""
5
+ type = type.split(";")[0]
6
+
7
+ if (type === "application/scim+json") {
8
+ ctx.req.headers["content-type"] = "application/json"
9
+ }
10
+
11
+ return next()
12
+ }
@@ -18,8 +18,17 @@ export const saveMetadata = async (
18
18
  if (existing) {
19
19
  metadata._rev = existing._rev
20
20
  }
21
- const res = await db.put(metadata)
22
- metadata._rev = res.rev
21
+ try {
22
+ const res = await db.put(metadata)
23
+ metadata._rev = res.rev
24
+ } catch (e: any) {
25
+ // account can be updated frequently
26
+ // ignore 409
27
+ if (e.status !== 409) {
28
+ throw e
29
+ }
30
+ }
31
+
23
32
  return metadata
24
33
  })
25
34
  }
@@ -1,5 +1,4 @@
1
1
  import env from "../../environment"
2
- import * as apps from "../../utilities/appService"
3
2
  import * as eventHelpers from "./events"
4
3
  import {
5
4
  accounts,
@@ -15,11 +14,12 @@ import {
15
14
  utils,
16
15
  ViewName,
17
16
  env as coreEnv,
17
+ context,
18
+ EmailUnavailableError,
18
19
  } from "@budibase/backend-core"
19
20
  import {
20
21
  AccountMetadata,
21
22
  AllDocsResponse,
22
- BulkUserResponse,
23
23
  CloudAccount,
24
24
  InviteUsersRequest,
25
25
  InviteUsersResponse,
@@ -28,9 +28,10 @@ import {
28
28
  PlatformUser,
29
29
  PlatformUserByEmail,
30
30
  RowResponse,
31
- SearchUsersRequest,
32
31
  User,
33
32
  SaveUserOpts,
33
+ BulkUserCreated,
34
+ BulkUserDeleted,
34
35
  Account,
35
36
  } from "@budibase/types"
36
37
  import { sendEmail } from "../../utilities/email"
@@ -38,8 +39,6 @@ import { EmailTemplatePurpose } from "../../constants"
38
39
  import * as pro from "@budibase/pro"
39
40
  import * as accountSdk from "../accounts"
40
41
 
41
- const PAGE_LIMIT = 8
42
-
43
42
  export const allUsers = async () => {
44
43
  const db = tenancy.getGlobalDB()
45
44
  const response = await db.allDocs(
@@ -69,43 +68,6 @@ export const getUsersByAppAccess = async (appId?: string) => {
69
68
  return response
70
69
  }
71
70
 
72
- export const paginatedUsers = async ({
73
- page,
74
- email,
75
- appId,
76
- }: SearchUsersRequest = {}) => {
77
- const db = tenancy.getGlobalDB()
78
- // get one extra document, to have the next page
79
- const opts: any = {
80
- include_docs: true,
81
- limit: PAGE_LIMIT + 1,
82
- }
83
- // add a startkey if the page was specified (anchor)
84
- if (page) {
85
- opts.startkey = page
86
- }
87
- // property specifies what to use for the page/anchor
88
- let userList,
89
- property = "_id",
90
- getKey
91
- if (appId) {
92
- userList = await usersCore.searchGlobalUsersByApp(appId, opts)
93
- getKey = (doc: any) => usersCore.getGlobalUserByAppPage(appId, doc)
94
- } else if (email) {
95
- userList = await usersCore.searchGlobalUsersByEmail(email, opts)
96
- property = "email"
97
- } else {
98
- // no search, query allDocs
99
- const response = await db.allDocs(dbUtils.getGlobalUserParams(null, opts))
100
- userList = response.rows.map((row: any) => row.doc)
101
- }
102
- return dbUtils.pagination(userList, PAGE_LIMIT, {
103
- paginate: true,
104
- property,
105
- getKey,
106
- })
107
- }
108
-
109
71
  export async function getUserByEmail(email: string) {
110
72
  return usersCore.getGlobalUserByEmail(email)
111
73
  }
@@ -198,7 +160,7 @@ const validateUniqueUser = async (email: string, tenantId: string) => {
198
160
  if (env.MULTI_TENANCY) {
199
161
  const tenantUser = await getPlatformUser(email)
200
162
  if (tenantUser != null && tenantUser.tenantId !== tenantId) {
201
- throw `Unavailable`
163
+ throw new EmailUnavailableError(email)
202
164
  }
203
165
  }
204
166
 
@@ -206,7 +168,7 @@ const validateUniqueUser = async (email: string, tenantId: string) => {
206
168
  if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
207
169
  const account = await accounts.getAccount(email)
208
170
  if (account && account.verified && account.tenantId !== tenantId) {
209
- throw `Unavailable`
171
+ throw new EmailUnavailableError(email)
210
172
  }
211
173
  }
212
174
  }
@@ -235,6 +197,8 @@ export async function isPreventPasswordActions(user: User, account?: Account) {
235
197
  return !!(account && account.email === user.email && isSSOAccount(account))
236
198
  }
237
199
 
200
+ // TODO: The single save should re-use the bulk insert with a single
201
+ // user so that we don't need to duplicate logic
238
202
  export const save = async (
239
203
  user: User,
240
204
  opts: SaveUserOpts = {}
@@ -277,60 +241,60 @@ export const save = async (
277
241
  // no id was specified - load from email instead
278
242
  dbUser = await usersCore.getGlobalUserByEmail(email)
279
243
  if (dbUser && dbUser._id !== _id) {
280
- throw `Unavailable`
244
+ throw new EmailUnavailableError(email)
281
245
  }
282
246
  }
283
247
 
284
- await validateUniqueUser(email, tenantId)
248
+ const change = dbUser ? 0 : 1 // no change if there is existing user
249
+ return pro.quotas.addUsers(change, async () => {
250
+ await validateUniqueUser(email, tenantId)
285
251
 
286
- let builtUser = await buildUser(user, opts, tenantId, dbUser)
287
- // don't allow a user to update its own roles/perms
288
- if (opts.currentUserId && opts.currentUserId === dbUser?._id) {
289
- builtUser.builder = dbUser.builder
290
- builtUser.admin = dbUser.admin
291
- builtUser.roles = dbUser.roles
292
- }
252
+ let builtUser = await buildUser(user, opts, tenantId, dbUser)
253
+ // don't allow a user to update its own roles/perms
254
+ if (opts.currentUserId && opts.currentUserId === dbUser?._id) {
255
+ builtUser.builder = dbUser.builder
256
+ builtUser.admin = dbUser.admin
257
+ builtUser.roles = dbUser.roles
258
+ }
293
259
 
294
- if (!dbUser && roles?.length) {
295
- builtUser.roles = { ...roles }
296
- }
260
+ if (!dbUser && roles?.length) {
261
+ builtUser.roles = { ...roles }
262
+ }
297
263
 
298
- // make sure we set the _id field for a new user
299
- // Also if this is a new user, associate groups with them
300
- let groupPromises = []
301
- if (!_id) {
302
- _id = builtUser._id!
264
+ // make sure we set the _id field for a new user
265
+ // Also if this is a new user, associate groups with them
266
+ let groupPromises = []
267
+ if (!_id) {
268
+ _id = builtUser._id!
303
269
 
304
- if (userGroups.length > 0) {
305
- for (let groupId of userGroups) {
306
- groupPromises.push(pro.groups.addUsers(groupId, [_id]))
270
+ if (userGroups.length > 0) {
271
+ for (let groupId of userGroups) {
272
+ groupPromises.push(pro.groups.addUsers(groupId, [_id]))
273
+ }
307
274
  }
308
275
  }
309
- }
310
-
311
- try {
312
- // save the user to db
313
- let response = await db.put(builtUser)
314
- builtUser._rev = response.rev
315
276
 
316
- await eventHelpers.handleSaveEvents(builtUser, dbUser)
317
- await platform.users.addUser(tenantId, builtUser._id!, builtUser.email)
318
- await cache.user.invalidateUser(response.id)
277
+ try {
278
+ // save the user to db
279
+ let response = await db.put(builtUser)
280
+ builtUser._rev = response.rev
319
281
 
320
- // let server know to sync user
321
- await apps.syncUserInApps(_id, dbUser)
282
+ await eventHelpers.handleSaveEvents(builtUser, dbUser)
283
+ await platform.users.addUser(tenantId, builtUser._id!, builtUser.email)
284
+ await cache.user.invalidateUser(response.id)
322
285
 
323
- await Promise.all(groupPromises)
286
+ await Promise.all(groupPromises)
324
287
 
325
- // finally returned the saved user from the db
326
- return db.get(builtUser._id!)
327
- } catch (err: any) {
328
- if (err.status === 409) {
329
- throw "User exists already"
330
- } else {
331
- throw err
288
+ // finally returned the saved user from the db
289
+ return db.get(builtUser._id!)
290
+ } catch (err: any) {
291
+ if (err.status === 409) {
292
+ throw "User exists already"
293
+ } else {
294
+ throw err
295
+ }
332
296
  }
333
- }
297
+ })
334
298
  }
335
299
 
336
300
  const getExistingTenantUsers = async (emails: string[]): Promise<User[]> => {
@@ -416,7 +380,7 @@ const searchExistingEmails = async (emails: string[]) => {
416
380
  export const bulkCreate = async (
417
381
  newUsersRequested: User[],
418
382
  groups: string[]
419
- ): Promise<BulkUserResponse["created"]> => {
383
+ ): Promise<BulkUserCreated> => {
420
384
  const tenantId = tenancy.getTenantId()
421
385
 
422
386
  let usersToSave: any[] = []
@@ -444,55 +408,56 @@ export const bulkCreate = async (
444
408
  }
445
409
 
446
410
  const account = await accountSdk.api.getAccountByTenantId(tenantId)
447
- // create the promises array that will be called by bulkDocs
448
- newUsers.forEach((user: any) => {
449
- usersToSave.push(
450
- buildUser(
451
- user,
452
- {
453
- hashPassword: true,
454
- requirePassword: user.requirePassword,
455
- },
456
- tenantId,
457
- undefined, // no dbUser
458
- account
411
+ return pro.quotas.addUsers(newUsers.length, async () => {
412
+ // create the promises array that will be called by bulkDocs
413
+ newUsers.forEach((user: any) => {
414
+ usersToSave.push(
415
+ buildUser(
416
+ user,
417
+ {
418
+ hashPassword: true,
419
+ requirePassword: user.requirePassword,
420
+ },
421
+ tenantId,
422
+ undefined, // no dbUser
423
+ account
424
+ )
459
425
  )
460
- )
461
- })
426
+ })
462
427
 
463
- const usersToBulkSave = await Promise.all(usersToSave)
464
- await usersCore.bulkUpdateGlobalUsers(usersToBulkSave)
428
+ const usersToBulkSave = await Promise.all(usersToSave)
429
+ await usersCore.bulkUpdateGlobalUsers(usersToBulkSave)
465
430
 
466
- // Post-processing of bulk added users, e.g. events and cache operations
467
- for (const user of usersToBulkSave) {
468
- // TODO: Refactor to bulk insert users into the info db
469
- // instead of relying on looping tenant creation
470
- await platform.users.addUser(tenantId, user._id, user.email)
471
- await eventHelpers.handleSaveEvents(user, undefined)
472
- await apps.syncUserInApps(user._id)
473
- }
474
-
475
- const saved = usersToBulkSave.map(user => {
476
- return {
477
- _id: user._id,
478
- email: user.email,
431
+ // Post-processing of bulk added users, e.g. events and cache operations
432
+ for (const user of usersToBulkSave) {
433
+ // TODO: Refactor to bulk insert users into the info db
434
+ // instead of relying on looping tenant creation
435
+ await platform.users.addUser(tenantId, user._id, user.email)
436
+ await eventHelpers.handleSaveEvents(user, undefined)
479
437
  }
480
- })
481
438
 
482
- // now update the groups
483
- if (Array.isArray(saved) && groups) {
484
- const groupPromises = []
485
- const createdUserIds = saved.map(user => user._id)
486
- for (let groupId of groups) {
487
- groupPromises.push(pro.groups.addUsers(groupId, createdUserIds))
439
+ const saved = usersToBulkSave.map(user => {
440
+ return {
441
+ _id: user._id,
442
+ email: user.email,
443
+ }
444
+ })
445
+
446
+ // now update the groups
447
+ if (Array.isArray(saved) && groups) {
448
+ const groupPromises = []
449
+ const createdUserIds = saved.map(user => user._id)
450
+ for (let groupId of groups) {
451
+ groupPromises.push(pro.groups.addUsers(groupId, createdUserIds))
452
+ }
453
+ await Promise.all(groupPromises)
488
454
  }
489
- await Promise.all(groupPromises)
490
- }
491
455
 
492
- return {
493
- successful: saved,
494
- unsuccessful,
495
- }
456
+ return {
457
+ successful: saved,
458
+ unsuccessful,
459
+ }
460
+ })
496
461
  }
497
462
 
498
463
  /**
@@ -517,10 +482,10 @@ const getAccountHolderFromUserIds = async (
517
482
 
518
483
  export const bulkDelete = async (
519
484
  userIds: string[]
520
- ): Promise<BulkUserResponse["deleted"]> => {
485
+ ): Promise<BulkUserDeleted> => {
521
486
  const db = tenancy.getGlobalDB()
522
487
 
523
- const response: BulkUserResponse["deleted"] = {
488
+ const response: BulkUserDeleted = {
524
489
  successful: [],
525
490
  unsuccessful: [],
526
491
  }
@@ -554,6 +519,8 @@ export const bulkDelete = async (
554
519
  _deleted: true,
555
520
  }))
556
521
  const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
522
+
523
+ await pro.quotas.removeUsers(toDelete.length)
557
524
  for (let user of usersToDelete) {
558
525
  await bulkDeleteProcessing(user)
559
526
  }
@@ -583,7 +550,9 @@ export const bulkDelete = async (
583
550
  return response
584
551
  }
585
552
 
586
- export const destroy = async (id: string, currentUser: any) => {
553
+ // TODO: The single delete should re-use the bulk delete with a single
554
+ // user so that we don't need to duplicate logic
555
+ export const destroy = async (id: string) => {
587
556
  const db = tenancy.getGlobalDB()
588
557
  const dbUser = (await db.get(id)) as User
589
558
  const userId = dbUser._id as string
@@ -593,7 +562,7 @@ export const destroy = async (id: string, currentUser: any) => {
593
562
  const email = dbUser.email
594
563
  const account = await accounts.getAccount(email)
595
564
  if (account) {
596
- if (email === currentUser.email) {
565
+ if (dbUser.userId === context.getIdentity()!._id) {
597
566
  throw new HTTPError('Please visit "Account" to delete this user', 400)
598
567
  } else {
599
568
  throw new HTTPError("Account holder cannot be deleted", 400)
@@ -605,11 +574,10 @@ export const destroy = async (id: string, currentUser: any) => {
605
574
 
606
575
  await db.remove(userId, dbUser._rev)
607
576
 
577
+ await pro.quotas.removeUsers(1)
608
578
  await eventHelpers.handleDeleteEvents(dbUser)
609
579
  await cache.user.invalidateUser(userId)
610
580
  await sessions.invalidateSessions(userId, { reason: "deletion" })
611
- // let server know to sync user
612
- await apps.syncUserInApps(userId, dbUser)
613
581
  }
614
582
 
615
583
  const bulkDeleteProcessing = async (dbUser: User) => {
@@ -618,8 +586,6 @@ const bulkDeleteProcessing = async (dbUser: User) => {
618
586
  await eventHelpers.handleDeleteEvents(dbUser)
619
587
  await cache.user.invalidateUser(userId)
620
588
  await sessions.invalidateSessions(userId, { reason: "bulk-deletion" })
621
- // let server know to sync user
622
- await apps.syncUserInApps(userId, dbUser)
623
589
  }
624
590
 
625
591
  export const invite = async (
@@ -20,9 +20,18 @@ import {
20
20
  auth,
21
21
  constants,
22
22
  env as coreEnv,
23
+ db as dbCore,
24
+ encryption,
25
+ utils,
23
26
  } from "@budibase/backend-core"
24
27
  import structures, { CSRF_TOKEN } from "./structures"
25
- import { SaveUserResponse, User, AuthToken } from "@budibase/types"
28
+ import {
29
+ SaveUserResponse,
30
+ User,
31
+ AuthToken,
32
+ SCIMConfig,
33
+ ConfigType,
34
+ } from "@budibase/types"
26
35
  import API from "./api"
27
36
 
28
37
  class TestConfiguration {
@@ -31,6 +40,7 @@ class TestConfiguration {
31
40
  api: API
32
41
  tenantId: string
33
42
  user?: User
43
+ apiKey?: string
34
44
  userPassword = "test"
35
45
 
36
46
  constructor(opts: { openServer: boolean } = { openServer: true }) {
@@ -49,6 +59,12 @@ class TestConfiguration {
49
59
  this.api = new API(this)
50
60
  }
51
61
 
62
+ async useNewTenant() {
63
+ this.tenantId = structures.tenant.id()
64
+
65
+ await this.beforeAll()
66
+ }
67
+
52
68
  getRequest() {
53
69
  return this.request
54
70
  }
@@ -201,6 +217,12 @@ class TestConfiguration {
201
217
  return { [constants.Header.API_KEY]: coreEnv.INTERNAL_API_KEY }
202
218
  }
203
219
 
220
+ bearerAPIHeaders() {
221
+ return {
222
+ [constants.Header.AUTHORIZATION]: `Bearer ${this.apiKey}`,
223
+ }
224
+ }
225
+
204
226
  adminOnlyResponse = () => {
205
227
  return { message: "Admin user only endpoint.", status: 403 }
206
228
  }
@@ -213,6 +235,20 @@ class TestConfiguration {
213
235
  })
214
236
  await context.doInTenant(this.tenantId!, async () => {
215
237
  this.user = await this.createUser(user)
238
+
239
+ const db = context.getGlobalDB()
240
+
241
+ const id = dbCore.generateDevInfoID(this.user._id)
242
+ // TODO: dry
243
+ this.apiKey = encryption.encrypt(
244
+ `${this.tenantId}${dbCore.SEPARATOR}${utils.newid()}`
245
+ )
246
+ const devInfo = {
247
+ _id: id,
248
+ userId: this.user._id,
249
+ apiKey: this.apiKey,
250
+ }
251
+ await db.put(devInfo)
216
252
  })
217
253
  }
218
254
 
@@ -305,6 +341,19 @@ class TestConfiguration {
305
341
  controllers.config.save
306
342
  )
307
343
  }
344
+
345
+ // CONFIGS - SCIM
346
+
347
+ async setSCIMConfig(enabled: boolean) {
348
+ await this.deleteConfig(Config.SCIM)
349
+ const config: SCIMConfig = {
350
+ type: ConfigType.SCIM,
351
+ config: { enabled },
352
+ }
353
+
354
+ await this._req(config, null, controllers.config.save)
355
+ return config
356
+ }
308
357
  }
309
358
 
310
359
  export default TestConfiguration
@@ -15,6 +15,9 @@ import { RolesAPI } from "./roles"
15
15
  import { TemplatesAPI } from "./templates"
16
16
  import { LicenseAPI } from "./license"
17
17
  import { AuditLogAPI } from "./auditLogs"
18
+ import { ScimUsersAPI } from "./scim/users"
19
+ import { ScimGroupsAPI } from "./scim/groups"
20
+
18
21
  export default class API {
19
22
  accounts: AccountAPI
20
23
  auth: AuthAPI
@@ -32,6 +35,8 @@ export default class API {
32
35
  templates: TemplatesAPI
33
36
  license: LicenseAPI
34
37
  auditLogs: AuditLogAPI
38
+ scimUsersAPI: ScimUsersAPI
39
+ scimGroupsAPI: ScimGroupsAPI
35
40
 
36
41
  constructor(config: TestConfiguration) {
37
42
  this.accounts = new AccountAPI(config)
@@ -50,5 +55,7 @@ export default class API {
50
55
  this.templates = new TemplatesAPI(config)
51
56
  this.license = new LicenseAPI(config)
52
57
  this.auditLogs = new AuditLogAPI(config)
58
+ this.scimUsersAPI = new ScimUsersAPI(config)
59
+ this.scimGroupsAPI = new ScimGroupsAPI(config)
53
60
  }
54
61
  }