@budibase/worker 2.3.20 → 2.3.21-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/__mocks__/node-fetch.ts +23 -0
  2. package/jest-testcontainers-config.js +8 -0
  3. package/jest.config.ts +14 -18
  4. package/package.json +12 -8
  5. package/scripts/dev/manage.js +1 -0
  6. package/scripts/test.sh +12 -0
  7. package/src/api/controllers/global/auth.ts +109 -111
  8. package/src/api/controllers/global/configs.ts +150 -142
  9. package/src/api/controllers/global/self.ts +28 -44
  10. package/src/api/controllers/global/users.ts +211 -32
  11. package/src/api/controllers/system/accounts.ts +7 -5
  12. package/src/api/controllers/system/tenants.ts +4 -8
  13. package/src/api/index.ts +4 -20
  14. package/src/api/routes/global/auth.ts +10 -7
  15. package/src/api/routes/global/configs.ts +6 -12
  16. package/src/api/routes/global/tests/auditLogs.spec.ts +111 -0
  17. package/src/api/routes/global/tests/auth.spec.ts +217 -18
  18. package/src/api/routes/global/tests/configs.spec.ts +93 -65
  19. package/src/api/routes/global/tests/realEmail.spec.ts +1 -0
  20. package/src/api/routes/global/tests/roles.spec.ts +20 -14
  21. package/src/api/routes/global/tests/self.spec.ts +3 -4
  22. package/src/api/routes/global/tests/users.spec.ts +29 -28
  23. package/src/api/routes/global/users.ts +19 -14
  24. package/src/api/routes/index.ts +3 -0
  25. package/src/api/routes/system/tenants.ts +1 -1
  26. package/src/api/routes/system/tests/accounts.spec.ts +4 -4
  27. package/src/api/routes/system/tests/migrations.spec.ts +2 -2
  28. package/src/api/routes/system/tests/restore.spec.ts +2 -2
  29. package/src/api/routes/system/tests/status.spec.ts +5 -5
  30. package/src/db/index.ts +8 -2
  31. package/src/ddApm.ts +7 -0
  32. package/src/elasticApm.ts +10 -0
  33. package/src/environment.ts +4 -0
  34. package/src/index.ts +29 -8
  35. package/src/middleware/tests/tenancy.spec.ts +4 -4
  36. package/src/migrations/functions/globalInfoSyncUsers.ts +4 -3
  37. package/src/sdk/accounts/index.ts +2 -1
  38. package/src/sdk/accounts/{accounts.ts → metadata.ts} +0 -1
  39. package/src/sdk/auth/auth.ts +86 -0
  40. package/src/sdk/auth/index.ts +1 -0
  41. package/src/sdk/tenants/index.ts +1 -0
  42. package/src/sdk/tenants/tenants.ts +76 -0
  43. package/src/sdk/users/events.ts +4 -0
  44. package/src/sdk/users/index.ts +1 -0
  45. package/src/sdk/users/tests/users.spec.ts +76 -0
  46. package/src/sdk/users/users.ts +86 -53
  47. package/src/tests/TestConfiguration.ts +41 -62
  48. package/src/tests/api/auditLogs.ts +26 -0
  49. package/src/tests/api/auth.ts +44 -14
  50. package/src/tests/api/base.ts +2 -1
  51. package/src/tests/api/configs.ts +9 -1
  52. package/src/tests/api/email.ts +1 -0
  53. package/src/tests/api/index.ts +3 -0
  54. package/src/tests/api/restore.ts +1 -0
  55. package/src/tests/api/users.ts +2 -2
  56. package/src/tests/jestEnv.ts +2 -1
  57. package/src/tests/jestSetup.ts +10 -6
  58. package/src/tests/logging.ts +34 -0
  59. package/src/tests/structures/configs.ts +20 -13
  60. package/src/tests/structures/index.ts +0 -4
  61. package/src/utilities/email.ts +22 -68
  62. package/src/utilities/redis.ts +42 -2
  63. package/src/utilities/templates.ts +4 -9
  64. package/coverage/clover.xml +0 -2199
  65. package/coverage/coverage-final.json +0 -84
  66. package/coverage/lcov-report/base.css +0 -224
  67. package/coverage/lcov-report/block-navigation.js +0 -87
  68. package/coverage/lcov-report/favicon.png +0 -0
  69. package/coverage/lcov-report/index.html +0 -431
  70. package/coverage/lcov-report/prettify.css +0 -1
  71. package/coverage/lcov-report/prettify.js +0 -2
  72. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  73. package/coverage/lcov-report/sorter.js +0 -196
  74. package/coverage/lcov-report/src/api/controllers/global/auth.ts.html +0 -934
  75. package/coverage/lcov-report/src/api/controllers/global/configs.ts.html +0 -1348
  76. package/coverage/lcov-report/src/api/controllers/global/email.ts.html +0 -196
  77. package/coverage/lcov-report/src/api/controllers/global/events.ts.html +0 -136
  78. package/coverage/lcov-report/src/api/controllers/global/index.html +0 -251
  79. package/coverage/lcov-report/src/api/controllers/global/license.ts.html +0 -187
  80. package/coverage/lcov-report/src/api/controllers/global/roles.ts.html +0 -283
  81. package/coverage/lcov-report/src/api/controllers/global/self.ts.html +0 -577
  82. package/coverage/lcov-report/src/api/controllers/global/templates.ts.html +0 -316
  83. package/coverage/lcov-report/src/api/controllers/global/users.ts.html +0 -838
  84. package/coverage/lcov-report/src/api/controllers/global/workspaces.ts.html +0 -244
  85. package/coverage/lcov-report/src/api/controllers/system/accounts.ts.html +0 -148
  86. package/coverage/lcov-report/src/api/controllers/system/environment.ts.html +0 -124
  87. package/coverage/lcov-report/src/api/controllers/system/index.html +0 -191
  88. package/coverage/lcov-report/src/api/controllers/system/migrations.ts.html +0 -124
  89. package/coverage/lcov-report/src/api/controllers/system/restore.ts.html +0 -124
  90. package/coverage/lcov-report/src/api/controllers/system/status.ts.html +0 -133
  91. package/coverage/lcov-report/src/api/controllers/system/tenants.ts.html +0 -154
  92. package/coverage/lcov-report/src/api/index.html +0 -116
  93. package/coverage/lcov-report/src/api/index.ts.html +0 -595
  94. package/coverage/lcov-report/src/api/routes/global/auth.ts.html +0 -349
  95. package/coverage/lcov-report/src/api/routes/global/configs.ts.html +0 -457
  96. package/coverage/lcov-report/src/api/routes/global/email.ts.html +0 -193
  97. package/coverage/lcov-report/src/api/routes/global/events.ts.html +0 -109
  98. package/coverage/lcov-report/src/api/routes/global/index.html +0 -251
  99. package/coverage/lcov-report/src/api/routes/global/license.ts.html +0 -124
  100. package/coverage/lcov-report/src/api/routes/global/roles.ts.html +0 -133
  101. package/coverage/lcov-report/src/api/routes/global/self.ts.html +0 -139
  102. package/coverage/lcov-report/src/api/routes/global/templates.ts.html +0 -196
  103. package/coverage/lcov-report/src/api/routes/global/users.ts.html +0 -475
  104. package/coverage/lcov-report/src/api/routes/global/workspaces.ts.html +0 -196
  105. package/coverage/lcov-report/src/api/routes/index.html +0 -116
  106. package/coverage/lcov-report/src/api/routes/index.ts.html +0 -202
  107. package/coverage/lcov-report/src/api/routes/system/accounts.ts.html +0 -142
  108. package/coverage/lcov-report/src/api/routes/system/environment.ts.html +0 -109
  109. package/coverage/lcov-report/src/api/routes/system/index.html +0 -191
  110. package/coverage/lcov-report/src/api/routes/system/migrations.ts.html +0 -142
  111. package/coverage/lcov-report/src/api/routes/system/restore.ts.html +0 -109
  112. package/coverage/lcov-report/src/api/routes/system/status.ts.html +0 -109
  113. package/coverage/lcov-report/src/api/routes/system/tenants.ts.html +0 -124
  114. package/coverage/lcov-report/src/api/routes/validation/index.html +0 -131
  115. package/coverage/lcov-report/src/api/routes/validation/index.ts.html +0 -88
  116. package/coverage/lcov-report/src/api/routes/validation/users.ts.html +0 -235
  117. package/coverage/lcov-report/src/constants/index.html +0 -116
  118. package/coverage/lcov-report/src/constants/index.ts.html +0 -637
  119. package/coverage/lcov-report/src/constants/templates/index.html +0 -116
  120. package/coverage/lcov-report/src/constants/templates/index.ts.html +0 -316
  121. package/coverage/lcov-report/src/db/index.html +0 -116
  122. package/coverage/lcov-report/src/db/index.ts.html +0 -115
  123. package/coverage/lcov-report/src/environment.ts.html +0 -388
  124. package/coverage/lcov-report/src/index.html +0 -131
  125. package/coverage/lcov-report/src/index.ts.html +0 -394
  126. package/coverage/lcov-report/src/middleware/cloudRestricted.ts.html +0 -139
  127. package/coverage/lcov-report/src/middleware/index.html +0 -116
  128. package/coverage/lcov-report/src/migrations/functions/globalInfoSyncUsers.ts.html +0 -145
  129. package/coverage/lcov-report/src/migrations/functions/index.html +0 -116
  130. package/coverage/lcov-report/src/migrations/index.html +0 -116
  131. package/coverage/lcov-report/src/migrations/index.ts.html +0 -271
  132. package/coverage/lcov-report/src/sdk/accounts/accounts.ts.html +0 -262
  133. package/coverage/lcov-report/src/sdk/accounts/index.html +0 -131
  134. package/coverage/lcov-report/src/sdk/accounts/index.ts.html +0 -88
  135. package/coverage/lcov-report/src/sdk/index.html +0 -116
  136. package/coverage/lcov-report/src/sdk/index.ts.html +0 -106
  137. package/coverage/lcov-report/src/sdk/users/events.ts.html +0 -601
  138. package/coverage/lcov-report/src/sdk/users/index.html +0 -146
  139. package/coverage/lcov-report/src/sdk/users/index.ts.html +0 -88
  140. package/coverage/lcov-report/src/sdk/users/users.ts.html +0 -1999
  141. package/coverage/lcov-report/src/tests/TestConfiguration.ts.html +0 -1075
  142. package/coverage/lcov-report/src/tests/api/accounts.ts.html +0 -160
  143. package/coverage/lcov-report/src/tests/api/auth.ts.html +0 -220
  144. package/coverage/lcov-report/src/tests/api/base.ts.html +0 -133
  145. package/coverage/lcov-report/src/tests/api/configs.ts.html +0 -226
  146. package/coverage/lcov-report/src/tests/api/email.ts.html +0 -148
  147. package/coverage/lcov-report/src/tests/api/environment.ts.html +0 -130
  148. package/coverage/lcov-report/src/tests/api/groups.ts.html +0 -163
  149. package/coverage/lcov-report/src/tests/api/index.html +0 -356
  150. package/coverage/lcov-report/src/tests/api/index.ts.html +0 -238
  151. package/coverage/lcov-report/src/tests/api/license.ts.html +0 -136
  152. package/coverage/lcov-report/src/tests/api/migrations.ts.html +0 -151
  153. package/coverage/lcov-report/src/tests/api/restore.ts.html +0 -127
  154. package/coverage/lcov-report/src/tests/api/roles.ts.html +0 -181
  155. package/coverage/lcov-report/src/tests/api/self.ts.html +0 -163
  156. package/coverage/lcov-report/src/tests/api/status.ts.html +0 -121
  157. package/coverage/lcov-report/src/tests/api/templates.ts.html +0 -175
  158. package/coverage/lcov-report/src/tests/api/tenants.ts.html +0 -130
  159. package/coverage/lcov-report/src/tests/api/users.ts.html +0 -514
  160. package/coverage/lcov-report/src/tests/controllers.ts.html +0 -100
  161. package/coverage/lcov-report/src/tests/index.html +0 -146
  162. package/coverage/lcov-report/src/tests/index.ts.html +0 -103
  163. package/coverage/lcov-report/src/tests/mocks/email.ts.html +0 -115
  164. package/coverage/lcov-report/src/tests/mocks/index.html +0 -131
  165. package/coverage/lcov-report/src/tests/mocks/index.ts.html +0 -106
  166. package/coverage/lcov-report/src/tests/structures/configs.ts.html +0 -313
  167. package/coverage/lcov-report/src/tests/structures/groups.ts.html +0 -118
  168. package/coverage/lcov-report/src/tests/structures/index.html +0 -161
  169. package/coverage/lcov-report/src/tests/structures/index.ts.html +0 -151
  170. package/coverage/lcov-report/src/tests/structures/users.ts.html +0 -196
  171. package/coverage/lcov-report/src/utilities/appService.ts.html +0 -208
  172. package/coverage/lcov-report/src/utilities/email.ts.html +0 -850
  173. package/coverage/lcov-report/src/utilities/fileSystem.ts.html +0 -100
  174. package/coverage/lcov-report/src/utilities/index.html +0 -206
  175. package/coverage/lcov-report/src/utilities/index.ts.html +0 -112
  176. package/coverage/lcov-report/src/utilities/redis.ts.html +0 -424
  177. package/coverage/lcov-report/src/utilities/templates.ts.html +0 -232
  178. package/coverage/lcov-report/src/utilities/users.ts.html +0 -133
  179. package/coverage/lcov.info +0 -4187
  180. package/src/tests/structures/users.ts +0 -37
@@ -1,31 +1,54 @@
1
- import { checkInviteCode } from "../../../utilities/redis"
2
- import sdk from "../../../sdk"
1
+ import {
2
+ checkInviteCode,
3
+ getInviteCodes,
4
+ updateInviteCode,
5
+ } from "../../../utilities/redis"
6
+ // import sdk from "../../../sdk"
7
+ import * as userSdk from "../../../sdk/users"
3
8
  import env from "../../../environment"
4
9
  import {
10
+ AcceptUserInviteRequest,
11
+ AcceptUserInviteResponse,
5
12
  BulkUserRequest,
6
13
  BulkUserResponse,
7
14
  CloudAccount,
8
15
  CreateAdminUserRequest,
16
+ CreateAdminUserResponse,
17
+ Ctx,
9
18
  InviteUserRequest,
10
19
  InviteUsersRequest,
20
+ MigrationType,
21
+ SaveUserResponse,
11
22
  SearchUsersRequest,
12
23
  User,
24
+ UserCtx,
13
25
  } from "@budibase/types"
14
26
  import {
15
27
  accounts,
16
28
  cache,
17
29
  errors,
18
30
  events,
31
+ migrations,
19
32
  tenancy,
33
+ platform,
20
34
  } from "@budibase/backend-core"
21
35
  import { checkAnyUserExists } from "../../../utilities/users"
36
+ import { isEmailConfigured } from "../../../utilities/email"
22
37
 
23
38
  const MAX_USERS_UPLOAD_LIMIT = 1000
24
39
 
25
- export const save = async (ctx: any) => {
40
+ export const save = async (ctx: UserCtx<User, SaveUserResponse>) => {
26
41
  try {
27
42
  const currentUserId = ctx.user._id
28
- ctx.body = await sdk.users.save(ctx.request.body, { currentUserId })
43
+ const requestUser = ctx.request.body
44
+
45
+ const user = await userSdk.save(requestUser, { currentUserId })
46
+
47
+ ctx.body = {
48
+ _id: user._id!,
49
+ _rev: user._rev!,
50
+ email: user.email,
51
+ }
29
52
  } catch (err: any) {
30
53
  ctx.throw(err.status || 400, err)
31
54
  }
@@ -35,7 +58,7 @@ const bulkDelete = async (userIds: string[], currentUserId: string) => {
35
58
  if (userIds?.indexOf(currentUserId) !== -1) {
36
59
  throw new Error("Unable to delete self.")
37
60
  }
38
- return await sdk.users.bulkDelete(userIds)
61
+ return await userSdk.bulkDelete(userIds)
39
62
  }
40
63
 
41
64
  const bulkCreate = async (users: User[], groupIds: string[]) => {
@@ -44,7 +67,7 @@ const bulkCreate = async (users: User[], groupIds: string[]) => {
44
67
  "Max limit for upload is 1000 users. Please reduce file size and try again."
45
68
  )
46
69
  }
47
- return await sdk.users.bulkCreate(users, groupIds)
70
+ return await userSdk.bulkCreate(users, groupIds)
48
71
  }
49
72
 
50
73
  export const bulkUpdate = async (ctx: any) => {
@@ -68,19 +91,30 @@ const parseBooleanParam = (param: any) => {
68
91
  return !(param && param === "false")
69
92
  }
70
93
 
71
- export const adminUser = async (ctx: any) => {
72
- const { email, password, tenantId } = ctx.request
73
- .body as CreateAdminUserRequest
94
+ export const adminUser = async (
95
+ ctx: Ctx<CreateAdminUserRequest, CreateAdminUserResponse>
96
+ ) => {
97
+ const { email, password, tenantId } = ctx.request.body
98
+
99
+ if (await platform.tenants.exists(tenantId)) {
100
+ ctx.throw(403, "Organisation already exists.")
101
+ }
102
+
103
+ if (env.MULTI_TENANCY) {
104
+ // store the new tenant record in the platform db
105
+ await platform.tenants.addTenant(tenantId)
106
+ await migrations.backPopulateMigrations({
107
+ type: MigrationType.GLOBAL,
108
+ tenantId,
109
+ })
110
+ }
111
+
74
112
  await tenancy.doInTenant(tenantId, async () => {
75
113
  // account portal sends a pre-hashed password - honour param to prevent double hashing
76
114
  const hashPassword = parseBooleanParam(ctx.request.query.hashPassword)
77
115
  // account portal sends no password for SSO users
78
116
  const requirePassword = parseBooleanParam(ctx.request.query.requirePassword)
79
117
 
80
- if (await tenancy.doesTenantExist(tenantId)) {
81
- ctx.throw(403, "Organisation already exists.")
82
- }
83
-
84
118
  const userExists = await checkAnyUserExists()
85
119
  if (userExists) {
86
120
  ctx.throw(
@@ -106,7 +140,7 @@ export const adminUser = async (ctx: any) => {
106
140
  // always bust checklist beforehand, if an error occurs but can proceed, don't get
107
141
  // stuck in a cycle
108
142
  await cache.bustCache(cache.CacheKey.CHECKLIST)
109
- const finalUser = await sdk.users.save(user, {
143
+ const finalUser = await userSdk.save(user, {
110
144
  hashPassword,
111
145
  requirePassword,
112
146
  })
@@ -118,7 +152,11 @@ export const adminUser = async (ctx: any) => {
118
152
  }
119
153
  await events.identification.identifyTenantGroup(tenantId, account)
120
154
 
121
- ctx.body = finalUser
155
+ ctx.body = {
156
+ _id: finalUser._id!,
157
+ _rev: finalUser._rev!,
158
+ email: finalUser.email,
159
+ }
122
160
  } catch (err: any) {
123
161
  ctx.throw(err.status || 400, err)
124
162
  }
@@ -128,7 +166,7 @@ export const adminUser = async (ctx: any) => {
128
166
  export const countByApp = async (ctx: any) => {
129
167
  const appId = ctx.params.appId
130
168
  try {
131
- ctx.body = await sdk.users.countUsersByApp(appId)
169
+ ctx.body = await userSdk.countUsersByApp(appId)
132
170
  } catch (err: any) {
133
171
  ctx.throw(err.status || 400, err)
134
172
  }
@@ -140,28 +178,40 @@ export const destroy = async (ctx: any) => {
140
178
  ctx.throw(400, "Unable to delete self.")
141
179
  }
142
180
 
143
- await sdk.users.destroy(id, ctx.user)
181
+ await userSdk.destroy(id, ctx.user)
144
182
 
145
183
  ctx.body = {
146
184
  message: `User ${id} deleted.`,
147
185
  }
148
186
  }
149
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
+
150
195
  export const search = async (ctx: any) => {
151
196
  const body = ctx.request.body as SearchUsersRequest
152
- const paginated = await sdk.users.paginatedUsers(body)
153
- // user hashed password shouldn't ever be returned
154
- for (let user of paginated.data) {
155
- if (user) {
156
- delete user.password
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
+ }
157
207
  }
208
+ ctx.body = paginated
158
209
  }
159
- ctx.body = paginated
160
210
  }
161
211
 
162
212
  // called internally by app server user fetch
163
213
  export const fetch = async (ctx: any) => {
164
- const all = await sdk.users.allUsers()
214
+ const all = await userSdk.allUsers()
165
215
  // user hashed password shouldn't ever be returned
166
216
  for (let user of all) {
167
217
  if (user) {
@@ -173,12 +223,12 @@ export const fetch = async (ctx: any) => {
173
223
 
174
224
  // called internally by app server user find
175
225
  export const find = async (ctx: any) => {
176
- ctx.body = await sdk.users.getUser(ctx.params.id)
226
+ ctx.body = await userSdk.getUser(ctx.params.id)
177
227
  }
178
228
 
179
229
  export const tenantUserLookup = async (ctx: any) => {
180
230
  const id = ctx.params.id
181
- const user = await sdk.users.getPlatformUser(id)
231
+ const user = await userSdk.getPlatformUser(id)
182
232
  if (user) {
183
233
  ctx.body = user
184
234
  } else {
@@ -186,9 +236,71 @@ export const tenantUserLookup = async (ctx: any) => {
186
236
  }
187
237
  }
188
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
+
189
299
  export const invite = async (ctx: any) => {
190
300
  const request = ctx.request.body as InviteUserRequest
191
- const response = await sdk.users.invite([request])
301
+
302
+ let multiRequest = [request] as InviteUsersRequest
303
+ const response = await userSdk.invite(multiRequest)
192
304
 
193
305
  // explicitly throw for single user invite
194
306
  if (response.unsuccessful.length) {
@@ -202,12 +314,14 @@ export const invite = async (ctx: any) => {
202
314
 
203
315
  ctx.body = {
204
316
  message: "Invitation has been sent.",
317
+ successful: response.successful,
318
+ unsuccessful: response.unsuccessful,
205
319
  }
206
320
  }
207
321
 
208
322
  export const inviteMultiple = async (ctx: any) => {
209
323
  const request = ctx.request.body as InviteUsersRequest
210
- ctx.body = await sdk.users.invite(request)
324
+ ctx.body = await userSdk.invite(request)
211
325
  }
212
326
 
213
327
  export const checkInvite = async (ctx: any) => {
@@ -223,24 +337,89 @@ export const checkInvite = async (ctx: any) => {
223
337
  }
224
338
  }
225
339
 
226
- export const inviteAccept = async (ctx: any) => {
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
+
387
+ export const inviteAccept = async (
388
+ ctx: Ctx<AcceptUserInviteRequest, AcceptUserInviteResponse>
389
+ ) => {
227
390
  const { inviteCode, password, firstName, lastName } = ctx.request.body
228
391
  try {
229
392
  // info is an extension of the user object that was stored by global
230
393
  const { email, info }: any = await checkInviteCode(inviteCode)
231
- ctx.body = await tenancy.doInTenant(info.tenantId, async () => {
232
- const saved = await sdk.users.save({
394
+ const user = await tenancy.doInTenant(info.tenantId, async () => {
395
+ let request = {
233
396
  firstName,
234
397
  lastName,
235
398
  password,
236
399
  email,
400
+ roles: info.apps,
401
+ tenantId: info.tenantId,
402
+ }
403
+
404
+ delete info.apps
405
+
406
+ request = {
407
+ ...request,
237
408
  ...info,
238
- })
409
+ }
410
+
411
+ const saved = await userSdk.save(request)
239
412
  const db = tenancy.getGlobalDB()
240
413
  const user = await db.get(saved._id)
241
414
  await events.user.inviteAccepted(user)
242
415
  return saved
243
416
  })
417
+
418
+ ctx.body = {
419
+ _id: user._id,
420
+ _rev: user._rev,
421
+ email: user.email,
422
+ }
244
423
  } catch (err: any) {
245
424
  if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
246
425
  // explicitly re-throw limit exceeded errors
@@ -1,21 +1,23 @@
1
1
  import { Account, AccountMetadata } from "@budibase/types"
2
- import sdk from "../../../sdk"
2
+ import * as accounts from "../../../sdk/accounts"
3
3
 
4
4
  export const save = async (ctx: any) => {
5
5
  const account = ctx.request.body as Account
6
6
  let metadata: AccountMetadata = {
7
- _id: sdk.accounts.formatAccountMetadataId(account.accountId),
7
+ _id: accounts.metadata.formatAccountMetadataId(account.accountId),
8
8
  email: account.email,
9
9
  }
10
10
 
11
- metadata = await sdk.accounts.saveMetadata(metadata)
11
+ metadata = await accounts.metadata.saveMetadata(metadata)
12
12
 
13
13
  ctx.body = metadata
14
14
  ctx.status = 200
15
15
  }
16
16
 
17
17
  export const destroy = async (ctx: any) => {
18
- const accountId = sdk.accounts.formatAccountMetadataId(ctx.params.accountId)
19
- await sdk.accounts.destroyMetadata(accountId)
18
+ const accountId = accounts.metadata.formatAccountMetadataId(
19
+ ctx.params.accountId
20
+ )
21
+ await accounts.metadata.destroyMetadata(accountId)
20
22
  ctx.status = 204
21
23
  }
@@ -1,8 +1,7 @@
1
- import { BBContext } from "@budibase/types"
2
- import { deprovisioning } from "@budibase/backend-core"
3
- import { quotas } from "@budibase/pro"
1
+ import { UserCtx } from "@budibase/types"
2
+ import * as tenantSdk from "../../../sdk/tenants"
4
3
 
5
- const _delete = async (ctx: BBContext) => {
4
+ export async function destroy(ctx: UserCtx) {
6
5
  const user = ctx.user!
7
6
  const tenantId = ctx.params.tenantId
8
7
 
@@ -11,13 +10,10 @@ const _delete = async (ctx: BBContext) => {
11
10
  }
12
11
 
13
12
  try {
14
- await quotas.bustCache()
15
- await deprovisioning.deleteTenant(tenantId)
13
+ await tenantSdk.deleteTenant(tenantId)
16
14
  ctx.status = 204
17
15
  } catch (err) {
18
16
  ctx.log.error(err)
19
17
  throw err
20
18
  }
21
19
  }
22
-
23
- export { _delete as delete }
package/src/api/index.ts CHANGED
@@ -3,8 +3,7 @@ const compress = require("koa-compress")
3
3
  const zlib = require("zlib")
4
4
  import { routes } from "./routes"
5
5
  import { middleware as pro } from "@budibase/pro"
6
- import { errors, auth, middleware } from "@budibase/backend-core"
7
- import { APIError } from "@budibase/types"
6
+ import { auth, middleware } from "@budibase/backend-core"
8
7
 
9
8
  const PUBLIC_ENDPOINTS = [
10
9
  // deprecated single tenant sso callback
@@ -109,7 +108,9 @@ const NO_TENANCY_ENDPOINTS = [
109
108
  const NO_CSRF_ENDPOINTS = [...PUBLIC_ENDPOINTS]
110
109
 
111
110
  const router: Router = new Router()
111
+
112
112
  router
113
+ .use(middleware.errorHandling)
113
114
  .use(
114
115
  compress({
115
116
  threshold: 2048,
@@ -136,29 +137,12 @@ router
136
137
  (!ctx.isAuthenticated || (ctx.user && !ctx.user.budibaseAccess)) &&
137
138
  !ctx.internal
138
139
  ) {
139
- ctx.throw(403, "Unauthorized - no public worker access")
140
+ ctx.throw(403, "Unauthorized")
140
141
  }
141
142
  return next()
142
143
  })
143
144
  .use(middleware.auditLog)
144
145
 
145
- // error handling middleware - TODO: This could be moved to backend-core
146
- router.use(async (ctx, next) => {
147
- try {
148
- await next()
149
- } catch (err: any) {
150
- ctx.log.error(err)
151
- ctx.status = err.status || err.statusCode || 500
152
- const error = errors.getPublicError(err)
153
- const body: APIError = {
154
- message: err.message,
155
- status: ctx.status,
156
- error,
157
- }
158
- ctx.body = body
159
- }
160
- })
161
-
162
146
  router.get("/health", ctx => (ctx.status = 200))
163
147
 
164
148
  // authenticated routes
@@ -33,7 +33,7 @@ router
33
33
  .post(
34
34
  "/api/global/auth/:tenantId/login",
35
35
  buildAuthValidation(),
36
- authController.authenticate
36
+ authController.login
37
37
  )
38
38
  .post("/api/global/auth/logout", authController.logout)
39
39
  .post(
@@ -68,21 +68,24 @@ router
68
68
 
69
69
  // GOOGLE - MULTI TENANT
70
70
  .get("/api/global/auth/:tenantId/google", authController.googlePreAuth)
71
- .get("/api/global/auth/:tenantId/google/callback", authController.googleAuth)
71
+ .get(
72
+ "/api/global/auth/:tenantId/google/callback",
73
+ authController.googleCallback
74
+ )
72
75
 
73
76
  // GOOGLE - SINGLE TENANT - DEPRECATED
74
- .get("/api/global/auth/google/callback", authController.googleAuth)
75
- .get("/api/admin/auth/google/callback", authController.googleAuth)
77
+ .get("/api/global/auth/google/callback", authController.googleCallback)
78
+ .get("/api/admin/auth/google/callback", authController.googleCallback)
76
79
 
77
80
  // OIDC - MULTI TENANT
78
81
  .get(
79
82
  "/api/global/auth/:tenantId/oidc/configs/:configId",
80
83
  authController.oidcPreAuth
81
84
  )
82
- .get("/api/global/auth/:tenantId/oidc/callback", authController.oidcAuth)
85
+ .get("/api/global/auth/:tenantId/oidc/callback", authController.oidcCallback)
83
86
 
84
87
  // OIDC - SINGLE TENANT - DEPRECATED
85
- .get("/api/global/auth/oidc/callback", authController.oidcAuth)
86
- .get("/api/admin/auth/oidc/callback", authController.oidcAuth)
88
+ .get("/api/global/auth/oidc/callback", authController.oidcCallback)
89
+ .get("/api/admin/auth/oidc/callback", authController.oidcCallback)
87
90
 
88
91
  export default router
@@ -34,8 +34,8 @@ function settingValidation() {
34
34
  function googleValidation() {
35
35
  // prettier-ignore
36
36
  return Joi.object({
37
- clientID: Joi.when('activated', { is: true, then: Joi.string().required() }),
38
- clientSecret: Joi.when('activated', { is: true, then: Joi.string().required() }),
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.when('activated', { is: true, then: Joi.string().required() }),
49
- clientSecret: Joi.when('activated', { is: true, then: Joi.string().required() }),
50
- configUrl: Joi.when('activated', { is: true, then: Joi.string().required() }),
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.when('activated', { is: true, then: Joi.string().required() }),
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
+ })