@budibase/worker 2.3.17 → 2.3.18-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 (166) hide show
  1. package/__mocks__/node-fetch.ts +23 -0
  2. package/jest-testcontainers-config.js +8 -0
  3. package/jest.config.ts +3 -2
  4. package/package.json +10 -7
  5. package/scripts/dev/manage.js +1 -0
  6. package/src/api/controllers/global/auth.ts +77 -70
  7. package/src/api/controllers/global/self.ts +28 -44
  8. package/src/api/controllers/global/users.ts +65 -25
  9. package/src/api/controllers/system/accounts.ts +7 -5
  10. package/src/api/controllers/system/tenants.ts +4 -8
  11. package/src/api/index.ts +4 -20
  12. package/src/api/routes/global/auth.ts +10 -7
  13. package/src/api/routes/global/tests/auth.spec.ts +226 -17
  14. package/src/api/routes/global/tests/configs.spec.ts +5 -5
  15. package/src/api/routes/global/tests/roles.spec.ts +20 -14
  16. package/src/api/routes/global/tests/self.spec.ts +3 -4
  17. package/src/api/routes/global/tests/users.spec.ts +24 -27
  18. package/src/api/routes/system/tenants.ts +1 -1
  19. package/src/api/routes/system/tests/accounts.spec.ts +4 -4
  20. package/src/api/routes/system/tests/migrations.spec.ts +2 -2
  21. package/src/api/routes/system/tests/restore.spec.ts +2 -2
  22. package/src/api/routes/system/tests/status.spec.ts +5 -5
  23. package/src/ddApm.ts +7 -0
  24. package/src/elasticApm.ts +10 -0
  25. package/src/environment.ts +15 -1
  26. package/src/index.ts +12 -7
  27. package/src/middleware/tests/tenancy.spec.ts +4 -4
  28. package/src/migrations/functions/globalInfoSyncUsers.ts +4 -3
  29. package/src/sdk/accounts/index.ts +2 -1
  30. package/src/sdk/accounts/{accounts.ts → metadata.ts} +0 -1
  31. package/src/sdk/auth/auth.ts +86 -0
  32. package/src/sdk/auth/index.ts +1 -0
  33. package/src/sdk/tenants/index.ts +1 -0
  34. package/src/sdk/tenants/tenants.ts +76 -0
  35. package/src/sdk/users/events.ts +4 -0
  36. package/src/sdk/users/index.ts +1 -0
  37. package/src/sdk/users/tests/users.spec.ts +52 -0
  38. package/src/sdk/users/users.ts +53 -46
  39. package/src/tests/TestConfiguration.ts +41 -62
  40. package/src/tests/api/auth.ts +42 -14
  41. package/src/tests/api/base.ts +2 -1
  42. package/src/tests/api/configs.ts +2 -2
  43. package/src/tests/api/restore.ts +1 -0
  44. package/src/tests/api/users.ts +2 -2
  45. package/src/tests/jestEnv.ts +2 -1
  46. package/src/tests/jestSetup.ts +10 -6
  47. package/src/tests/logging.ts +34 -0
  48. package/src/tests/structures/index.ts +0 -4
  49. package/src/utilities/email.ts +3 -3
  50. package/coverage/clover.xml +0 -2199
  51. package/coverage/coverage-final.json +0 -84
  52. package/coverage/lcov-report/base.css +0 -224
  53. package/coverage/lcov-report/block-navigation.js +0 -87
  54. package/coverage/lcov-report/favicon.png +0 -0
  55. package/coverage/lcov-report/index.html +0 -431
  56. package/coverage/lcov-report/prettify.css +0 -1
  57. package/coverage/lcov-report/prettify.js +0 -2
  58. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  59. package/coverage/lcov-report/sorter.js +0 -196
  60. package/coverage/lcov-report/src/api/controllers/global/auth.ts.html +0 -934
  61. package/coverage/lcov-report/src/api/controllers/global/configs.ts.html +0 -1348
  62. package/coverage/lcov-report/src/api/controllers/global/email.ts.html +0 -196
  63. package/coverage/lcov-report/src/api/controllers/global/events.ts.html +0 -136
  64. package/coverage/lcov-report/src/api/controllers/global/index.html +0 -251
  65. package/coverage/lcov-report/src/api/controllers/global/license.ts.html +0 -187
  66. package/coverage/lcov-report/src/api/controllers/global/roles.ts.html +0 -283
  67. package/coverage/lcov-report/src/api/controllers/global/self.ts.html +0 -577
  68. package/coverage/lcov-report/src/api/controllers/global/templates.ts.html +0 -316
  69. package/coverage/lcov-report/src/api/controllers/global/users.ts.html +0 -838
  70. package/coverage/lcov-report/src/api/controllers/global/workspaces.ts.html +0 -244
  71. package/coverage/lcov-report/src/api/controllers/system/accounts.ts.html +0 -148
  72. package/coverage/lcov-report/src/api/controllers/system/environment.ts.html +0 -124
  73. package/coverage/lcov-report/src/api/controllers/system/index.html +0 -191
  74. package/coverage/lcov-report/src/api/controllers/system/migrations.ts.html +0 -124
  75. package/coverage/lcov-report/src/api/controllers/system/restore.ts.html +0 -124
  76. package/coverage/lcov-report/src/api/controllers/system/status.ts.html +0 -133
  77. package/coverage/lcov-report/src/api/controllers/system/tenants.ts.html +0 -154
  78. package/coverage/lcov-report/src/api/index.html +0 -116
  79. package/coverage/lcov-report/src/api/index.ts.html +0 -595
  80. package/coverage/lcov-report/src/api/routes/global/auth.ts.html +0 -349
  81. package/coverage/lcov-report/src/api/routes/global/configs.ts.html +0 -457
  82. package/coverage/lcov-report/src/api/routes/global/email.ts.html +0 -193
  83. package/coverage/lcov-report/src/api/routes/global/events.ts.html +0 -109
  84. package/coverage/lcov-report/src/api/routes/global/index.html +0 -251
  85. package/coverage/lcov-report/src/api/routes/global/license.ts.html +0 -124
  86. package/coverage/lcov-report/src/api/routes/global/roles.ts.html +0 -133
  87. package/coverage/lcov-report/src/api/routes/global/self.ts.html +0 -139
  88. package/coverage/lcov-report/src/api/routes/global/templates.ts.html +0 -196
  89. package/coverage/lcov-report/src/api/routes/global/users.ts.html +0 -475
  90. package/coverage/lcov-report/src/api/routes/global/workspaces.ts.html +0 -196
  91. package/coverage/lcov-report/src/api/routes/index.html +0 -116
  92. package/coverage/lcov-report/src/api/routes/index.ts.html +0 -202
  93. package/coverage/lcov-report/src/api/routes/system/accounts.ts.html +0 -142
  94. package/coverage/lcov-report/src/api/routes/system/environment.ts.html +0 -109
  95. package/coverage/lcov-report/src/api/routes/system/index.html +0 -191
  96. package/coverage/lcov-report/src/api/routes/system/migrations.ts.html +0 -142
  97. package/coverage/lcov-report/src/api/routes/system/restore.ts.html +0 -109
  98. package/coverage/lcov-report/src/api/routes/system/status.ts.html +0 -109
  99. package/coverage/lcov-report/src/api/routes/system/tenants.ts.html +0 -124
  100. package/coverage/lcov-report/src/api/routes/validation/index.html +0 -131
  101. package/coverage/lcov-report/src/api/routes/validation/index.ts.html +0 -88
  102. package/coverage/lcov-report/src/api/routes/validation/users.ts.html +0 -235
  103. package/coverage/lcov-report/src/constants/index.html +0 -116
  104. package/coverage/lcov-report/src/constants/index.ts.html +0 -637
  105. package/coverage/lcov-report/src/constants/templates/index.html +0 -116
  106. package/coverage/lcov-report/src/constants/templates/index.ts.html +0 -316
  107. package/coverage/lcov-report/src/db/index.html +0 -116
  108. package/coverage/lcov-report/src/db/index.ts.html +0 -115
  109. package/coverage/lcov-report/src/environment.ts.html +0 -388
  110. package/coverage/lcov-report/src/index.html +0 -131
  111. package/coverage/lcov-report/src/index.ts.html +0 -394
  112. package/coverage/lcov-report/src/middleware/cloudRestricted.ts.html +0 -139
  113. package/coverage/lcov-report/src/middleware/index.html +0 -116
  114. package/coverage/lcov-report/src/migrations/functions/globalInfoSyncUsers.ts.html +0 -145
  115. package/coverage/lcov-report/src/migrations/functions/index.html +0 -116
  116. package/coverage/lcov-report/src/migrations/index.html +0 -116
  117. package/coverage/lcov-report/src/migrations/index.ts.html +0 -271
  118. package/coverage/lcov-report/src/sdk/accounts/accounts.ts.html +0 -262
  119. package/coverage/lcov-report/src/sdk/accounts/index.html +0 -131
  120. package/coverage/lcov-report/src/sdk/accounts/index.ts.html +0 -88
  121. package/coverage/lcov-report/src/sdk/index.html +0 -116
  122. package/coverage/lcov-report/src/sdk/index.ts.html +0 -106
  123. package/coverage/lcov-report/src/sdk/users/events.ts.html +0 -601
  124. package/coverage/lcov-report/src/sdk/users/index.html +0 -146
  125. package/coverage/lcov-report/src/sdk/users/index.ts.html +0 -88
  126. package/coverage/lcov-report/src/sdk/users/users.ts.html +0 -1999
  127. package/coverage/lcov-report/src/tests/TestConfiguration.ts.html +0 -1075
  128. package/coverage/lcov-report/src/tests/api/accounts.ts.html +0 -160
  129. package/coverage/lcov-report/src/tests/api/auth.ts.html +0 -220
  130. package/coverage/lcov-report/src/tests/api/base.ts.html +0 -133
  131. package/coverage/lcov-report/src/tests/api/configs.ts.html +0 -226
  132. package/coverage/lcov-report/src/tests/api/email.ts.html +0 -148
  133. package/coverage/lcov-report/src/tests/api/environment.ts.html +0 -130
  134. package/coverage/lcov-report/src/tests/api/groups.ts.html +0 -163
  135. package/coverage/lcov-report/src/tests/api/index.html +0 -356
  136. package/coverage/lcov-report/src/tests/api/index.ts.html +0 -238
  137. package/coverage/lcov-report/src/tests/api/license.ts.html +0 -136
  138. package/coverage/lcov-report/src/tests/api/migrations.ts.html +0 -151
  139. package/coverage/lcov-report/src/tests/api/restore.ts.html +0 -127
  140. package/coverage/lcov-report/src/tests/api/roles.ts.html +0 -181
  141. package/coverage/lcov-report/src/tests/api/self.ts.html +0 -163
  142. package/coverage/lcov-report/src/tests/api/status.ts.html +0 -121
  143. package/coverage/lcov-report/src/tests/api/templates.ts.html +0 -175
  144. package/coverage/lcov-report/src/tests/api/tenants.ts.html +0 -130
  145. package/coverage/lcov-report/src/tests/api/users.ts.html +0 -514
  146. package/coverage/lcov-report/src/tests/controllers.ts.html +0 -100
  147. package/coverage/lcov-report/src/tests/index.html +0 -146
  148. package/coverage/lcov-report/src/tests/index.ts.html +0 -103
  149. package/coverage/lcov-report/src/tests/mocks/email.ts.html +0 -115
  150. package/coverage/lcov-report/src/tests/mocks/index.html +0 -131
  151. package/coverage/lcov-report/src/tests/mocks/index.ts.html +0 -106
  152. package/coverage/lcov-report/src/tests/structures/configs.ts.html +0 -313
  153. package/coverage/lcov-report/src/tests/structures/groups.ts.html +0 -118
  154. package/coverage/lcov-report/src/tests/structures/index.html +0 -161
  155. package/coverage/lcov-report/src/tests/structures/index.ts.html +0 -151
  156. package/coverage/lcov-report/src/tests/structures/users.ts.html +0 -196
  157. package/coverage/lcov-report/src/utilities/appService.ts.html +0 -208
  158. package/coverage/lcov-report/src/utilities/email.ts.html +0 -850
  159. package/coverage/lcov-report/src/utilities/fileSystem.ts.html +0 -100
  160. package/coverage/lcov-report/src/utilities/index.html +0 -206
  161. package/coverage/lcov-report/src/utilities/index.ts.html +0 -112
  162. package/coverage/lcov-report/src/utilities/redis.ts.html +0 -424
  163. package/coverage/lcov-report/src/utilities/templates.ts.html +0 -232
  164. package/coverage/lcov-report/src/utilities/users.ts.html +0 -133
  165. package/coverage/lcov.info +0 -4187
  166. package/src/tests/structures/users.ts +0 -37
@@ -1,31 +1,48 @@
1
1
  import { checkInviteCode } from "../../../utilities/redis"
2
- import sdk from "../../../sdk"
2
+ import * as userSdk from "../../../sdk/users"
3
3
  import env from "../../../environment"
4
4
  import {
5
+ AcceptUserInviteRequest,
6
+ AcceptUserInviteResponse,
5
7
  BulkUserRequest,
6
8
  BulkUserResponse,
7
9
  CloudAccount,
8
10
  CreateAdminUserRequest,
11
+ CreateAdminUserResponse,
12
+ Ctx,
9
13
  InviteUserRequest,
10
14
  InviteUsersRequest,
15
+ MigrationType,
16
+ SaveUserResponse,
11
17
  SearchUsersRequest,
12
18
  User,
19
+ UserCtx,
13
20
  } from "@budibase/types"
14
21
  import {
15
22
  accounts,
16
23
  cache,
17
24
  errors,
18
25
  events,
26
+ migrations,
19
27
  tenancy,
28
+ platform,
20
29
  } from "@budibase/backend-core"
21
30
  import { checkAnyUserExists } from "../../../utilities/users"
22
31
 
23
32
  const MAX_USERS_UPLOAD_LIMIT = 1000
24
33
 
25
- export const save = async (ctx: any) => {
34
+ export const save = async (ctx: UserCtx<User, SaveUserResponse>) => {
26
35
  try {
27
36
  const currentUserId = ctx.user._id
28
- ctx.body = await sdk.users.save(ctx.request.body, { currentUserId })
37
+ const requestUser = ctx.request.body
38
+
39
+ const user = await userSdk.save(requestUser, { currentUserId })
40
+
41
+ ctx.body = {
42
+ _id: user._id!,
43
+ _rev: user._rev!,
44
+ email: user.email,
45
+ }
29
46
  } catch (err: any) {
30
47
  ctx.throw(err.status || 400, err)
31
48
  }
@@ -35,7 +52,7 @@ const bulkDelete = async (userIds: string[], currentUserId: string) => {
35
52
  if (userIds?.indexOf(currentUserId) !== -1) {
36
53
  throw new Error("Unable to delete self.")
37
54
  }
38
- return await sdk.users.bulkDelete(userIds)
55
+ return await userSdk.bulkDelete(userIds)
39
56
  }
40
57
 
41
58
  const bulkCreate = async (users: User[], groupIds: string[]) => {
@@ -44,7 +61,7 @@ const bulkCreate = async (users: User[], groupIds: string[]) => {
44
61
  "Max limit for upload is 1000 users. Please reduce file size and try again."
45
62
  )
46
63
  }
47
- return await sdk.users.bulkCreate(users, groupIds)
64
+ return await userSdk.bulkCreate(users, groupIds)
48
65
  }
49
66
 
50
67
  export const bulkUpdate = async (ctx: any) => {
@@ -68,19 +85,30 @@ const parseBooleanParam = (param: any) => {
68
85
  return !(param && param === "false")
69
86
  }
70
87
 
71
- export const adminUser = async (ctx: any) => {
72
- const { email, password, tenantId } = ctx.request
73
- .body as CreateAdminUserRequest
88
+ export const adminUser = async (
89
+ ctx: Ctx<CreateAdminUserRequest, CreateAdminUserResponse>
90
+ ) => {
91
+ const { email, password, tenantId } = ctx.request.body
92
+
93
+ if (await platform.tenants.exists(tenantId)) {
94
+ ctx.throw(403, "Organisation already exists.")
95
+ }
96
+
97
+ if (env.MULTI_TENANCY) {
98
+ // store the new tenant record in the platform db
99
+ await platform.tenants.addTenant(tenantId)
100
+ await migrations.backPopulateMigrations({
101
+ type: MigrationType.GLOBAL,
102
+ tenantId,
103
+ })
104
+ }
105
+
74
106
  await tenancy.doInTenant(tenantId, async () => {
75
107
  // account portal sends a pre-hashed password - honour param to prevent double hashing
76
108
  const hashPassword = parseBooleanParam(ctx.request.query.hashPassword)
77
109
  // account portal sends no password for SSO users
78
110
  const requirePassword = parseBooleanParam(ctx.request.query.requirePassword)
79
111
 
80
- if (await tenancy.doesTenantExist(tenantId)) {
81
- ctx.throw(403, "Organisation already exists.")
82
- }
83
-
84
112
  const userExists = await checkAnyUserExists()
85
113
  if (userExists) {
86
114
  ctx.throw(
@@ -106,7 +134,7 @@ export const adminUser = async (ctx: any) => {
106
134
  // always bust checklist beforehand, if an error occurs but can proceed, don't get
107
135
  // stuck in a cycle
108
136
  await cache.bustCache(cache.CacheKey.CHECKLIST)
109
- const finalUser = await sdk.users.save(user, {
137
+ const finalUser = await userSdk.save(user, {
110
138
  hashPassword,
111
139
  requirePassword,
112
140
  })
@@ -118,7 +146,11 @@ export const adminUser = async (ctx: any) => {
118
146
  }
119
147
  await events.identification.identifyTenantGroup(tenantId, account)
120
148
 
121
- ctx.body = finalUser
149
+ ctx.body = {
150
+ _id: finalUser._id!,
151
+ _rev: finalUser._rev!,
152
+ email: finalUser.email,
153
+ }
122
154
  } catch (err: any) {
123
155
  ctx.throw(err.status || 400, err)
124
156
  }
@@ -128,7 +160,7 @@ export const adminUser = async (ctx: any) => {
128
160
  export const countByApp = async (ctx: any) => {
129
161
  const appId = ctx.params.appId
130
162
  try {
131
- ctx.body = await sdk.users.countUsersByApp(appId)
163
+ ctx.body = await userSdk.countUsersByApp(appId)
132
164
  } catch (err: any) {
133
165
  ctx.throw(err.status || 400, err)
134
166
  }
@@ -140,7 +172,7 @@ export const destroy = async (ctx: any) => {
140
172
  ctx.throw(400, "Unable to delete self.")
141
173
  }
142
174
 
143
- await sdk.users.destroy(id, ctx.user)
175
+ await userSdk.destroy(id, ctx.user)
144
176
 
145
177
  ctx.body = {
146
178
  message: `User ${id} deleted.`,
@@ -149,7 +181,7 @@ export const destroy = async (ctx: any) => {
149
181
 
150
182
  export const search = async (ctx: any) => {
151
183
  const body = ctx.request.body as SearchUsersRequest
152
- const paginated = await sdk.users.paginatedUsers(body)
184
+ const paginated = await userSdk.paginatedUsers(body)
153
185
  // user hashed password shouldn't ever be returned
154
186
  for (let user of paginated.data) {
155
187
  if (user) {
@@ -161,7 +193,7 @@ export const search = async (ctx: any) => {
161
193
 
162
194
  // called internally by app server user fetch
163
195
  export const fetch = async (ctx: any) => {
164
- const all = await sdk.users.allUsers()
196
+ const all = await userSdk.allUsers()
165
197
  // user hashed password shouldn't ever be returned
166
198
  for (let user of all) {
167
199
  if (user) {
@@ -173,12 +205,12 @@ export const fetch = async (ctx: any) => {
173
205
 
174
206
  // called internally by app server user find
175
207
  export const find = async (ctx: any) => {
176
- ctx.body = await sdk.users.getUser(ctx.params.id)
208
+ ctx.body = await userSdk.getUser(ctx.params.id)
177
209
  }
178
210
 
179
211
  export const tenantUserLookup = async (ctx: any) => {
180
212
  const id = ctx.params.id
181
- const user = await sdk.users.getPlatformUser(id)
213
+ const user = await userSdk.getPlatformUser(id)
182
214
  if (user) {
183
215
  ctx.body = user
184
216
  } else {
@@ -188,7 +220,7 @@ export const tenantUserLookup = async (ctx: any) => {
188
220
 
189
221
  export const invite = async (ctx: any) => {
190
222
  const request = ctx.request.body as InviteUserRequest
191
- const response = await sdk.users.invite([request])
223
+ const response = await userSdk.invite([request])
192
224
 
193
225
  // explicitly throw for single user invite
194
226
  if (response.unsuccessful.length) {
@@ -207,7 +239,7 @@ export const invite = async (ctx: any) => {
207
239
 
208
240
  export const inviteMultiple = async (ctx: any) => {
209
241
  const request = ctx.request.body as InviteUsersRequest
210
- ctx.body = await sdk.users.invite(request)
242
+ ctx.body = await userSdk.invite(request)
211
243
  }
212
244
 
213
245
  export const checkInvite = async (ctx: any) => {
@@ -223,13 +255,15 @@ export const checkInvite = async (ctx: any) => {
223
255
  }
224
256
  }
225
257
 
226
- export const inviteAccept = async (ctx: any) => {
258
+ export const inviteAccept = async (
259
+ ctx: Ctx<AcceptUserInviteRequest, AcceptUserInviteResponse>
260
+ ) => {
227
261
  const { inviteCode, password, firstName, lastName } = ctx.request.body
228
262
  try {
229
263
  // info is an extension of the user object that was stored by global
230
264
  const { email, info }: any = await checkInviteCode(inviteCode)
231
- ctx.body = await tenancy.doInTenant(info.tenantId, async () => {
232
- const saved = await sdk.users.save({
265
+ const user = await tenancy.doInTenant(info.tenantId, async () => {
266
+ const saved = await userSdk.save({
233
267
  firstName,
234
268
  lastName,
235
269
  password,
@@ -241,6 +275,12 @@ export const inviteAccept = async (ctx: any) => {
241
275
  await events.user.inviteAccepted(user)
242
276
  return saved
243
277
  })
278
+
279
+ ctx.body = {
280
+ _id: user._id,
281
+ _rev: user._rev,
282
+ email: user.email,
283
+ }
244
284
  } catch (err: any) {
245
285
  if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
246
286
  // 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
@@ -1,12 +1,27 @@
1
+ import { CloudAccount, SSOUser, User } from "@budibase/types"
2
+
1
3
  jest.mock("nodemailer")
2
- import { TestConfiguration, mocks } from "../../../../tests"
4
+ import {
5
+ TestConfiguration,
6
+ mocks,
7
+ structures,
8
+ generator,
9
+ } from "../../../../tests"
3
10
  const sendMailMock = mocks.email.mock()
4
- import { events } from "@budibase/backend-core"
11
+ import { events, constants } from "@budibase/backend-core"
12
+ import { Response } from "superagent"
13
+
14
+ import * as userSdk from "../../../../sdk/users"
15
+
16
+ function getAuthCookie(response: Response) {
17
+ return response.headers["set-cookie"]
18
+ .find((s: string) => s.startsWith(`${constants.Cookie.Auth}=`))
19
+ .split("=")[1]
20
+ .split(";")[0]
21
+ }
5
22
 
6
- const expectSetAuthCookie = (res: any) => {
7
- expect(
8
- res.get("Set-Cookie").find((c: string) => c.startsWith("budibase:auth"))
9
- ).toBeDefined()
23
+ const expectSetAuthCookie = (response: Response) => {
24
+ expect(getAuthCookie(response).length > 1).toBe(true)
10
25
  }
11
26
 
12
27
  describe("/api/global/auth", () => {
@@ -24,53 +39,247 @@ describe("/api/global/auth", () => {
24
39
  jest.clearAllMocks()
25
40
  })
26
41
 
42
+ async function createSSOUser() {
43
+ return config.doInTenant(async () => {
44
+ return userSdk.save(structures.users.ssoUser(), {
45
+ requirePassword: false,
46
+ })
47
+ })
48
+ }
49
+
27
50
  describe("password", () => {
28
51
  describe("POST /api/global/auth/:tenantId/login", () => {
29
- it("should login", () => {})
52
+ it("logs in with correct credentials", async () => {
53
+ const tenantId = config.tenantId!
54
+ const email = config.user?.email!
55
+ const password = config.userPassword
56
+
57
+ const response = await config.api.auth.login(tenantId, email, password)
58
+
59
+ expectSetAuthCookie(response)
60
+ expect(events.auth.login).toBeCalledTimes(1)
61
+ })
62
+
63
+ it("should return 403 with incorrect credentials", async () => {
64
+ const tenantId = config.tenantId!
65
+ const email = config.user?.email!
66
+ const password = "incorrect"
67
+
68
+ const response = await config.api.auth.login(
69
+ tenantId,
70
+ email,
71
+ password,
72
+ { status: 403 }
73
+ )
74
+ expect(response.body).toEqual({
75
+ message: "Invalid credentials",
76
+ status: 403,
77
+ })
78
+ })
79
+
80
+ it("should return 403 when user doesn't exist", async () => {
81
+ const tenantId = config.tenantId!
82
+ const email = "invaliduser@test.com"
83
+ const password = "password"
84
+
85
+ const response = await config.api.auth.login(
86
+ tenantId,
87
+ email,
88
+ password,
89
+ { status: 403 }
90
+ )
91
+ expect(response.body).toEqual({
92
+ message: "Invalid credentials",
93
+ status: 403,
94
+ })
95
+ })
96
+
97
+ describe("sso user", () => {
98
+ let user: User
99
+
100
+ async function testSSOUser() {
101
+ const tenantId = user.tenantId!
102
+ const email = user.email
103
+ const password = "test"
104
+
105
+ const response = await config.api.auth.login(
106
+ tenantId,
107
+ email,
108
+ password,
109
+ { status: 400 }
110
+ )
111
+
112
+ expect(response.body).toEqual({
113
+ message: "SSO user cannot login using password",
114
+ status: 400,
115
+ })
116
+ }
117
+
118
+ describe("budibase sso user", () => {
119
+ it("should prevent user from logging in", async () => {
120
+ user = await createSSOUser()
121
+ await testSSOUser()
122
+ })
123
+ })
124
+
125
+ describe("root account sso user", () => {
126
+ it("should prevent user from logging in", async () => {
127
+ user = await config.createUser()
128
+ const account = structures.accounts.ssoAccount() as CloudAccount
129
+ mocks.accounts.getAccount.mockReturnValueOnce(
130
+ Promise.resolve(account)
131
+ )
132
+
133
+ await testSSOUser()
134
+ })
135
+ })
136
+ })
30
137
  })
31
138
 
32
139
  describe("POST /api/global/auth/logout", () => {
33
140
  it("should logout", async () => {
34
- await config.api.auth.logout()
141
+ const response = await config.api.auth.logout()
35
142
  expect(events.auth.logout).toBeCalledTimes(1)
36
143
 
37
- // TODO: Verify sessions deleted
144
+ const authCookie = getAuthCookie(response)
145
+ expect(authCookie).toBe("")
38
146
  })
39
147
  })
40
148
 
41
149
  describe("POST /api/global/auth/:tenantId/reset", () => {
42
150
  it("should generate password reset email", async () => {
151
+ const user = await config.createUser()
152
+
43
153
  const { res, code } = await config.api.auth.requestPasswordReset(
44
- sendMailMock
154
+ sendMailMock,
155
+ user.email
45
156
  )
46
- const user = await config.getUser("test@test.com")
47
157
 
48
158
  expect(res.body).toEqual({
49
159
  message: "Please check your email for a reset link.",
50
160
  })
51
161
  expect(sendMailMock).toHaveBeenCalled()
52
-
53
162
  expect(code).toBeDefined()
54
163
  expect(events.user.passwordResetRequested).toBeCalledTimes(1)
55
164
  expect(events.user.passwordResetRequested).toBeCalledWith(user)
56
165
  })
166
+
167
+ describe("sso user", () => {
168
+ let user: User
169
+
170
+ async function testSSOUser() {
171
+ const { res } = await config.api.auth.requestPasswordReset(
172
+ sendMailMock,
173
+ user.email,
174
+ { status: 400 }
175
+ )
176
+
177
+ expect(res.body).toEqual({
178
+ message: "SSO user cannot reset password",
179
+ status: 400,
180
+ error: {
181
+ code: "http",
182
+ type: "generic",
183
+ },
184
+ })
185
+ expect(sendMailMock).not.toHaveBeenCalled()
186
+ }
187
+
188
+ describe("budibase sso user", () => {
189
+ it("should prevent user from generating password reset email", async () => {
190
+ user = await createSSOUser()
191
+ await testSSOUser()
192
+ })
193
+ })
194
+
195
+ describe("root account sso user", () => {
196
+ it("should prevent user from generating password reset email", async () => {
197
+ user = await config.createUser(structures.users.user())
198
+ const account = structures.accounts.ssoAccount() as CloudAccount
199
+ mocks.accounts.getAccount.mockReturnValueOnce(
200
+ Promise.resolve(account)
201
+ )
202
+
203
+ await testSSOUser()
204
+ })
205
+ })
206
+ })
57
207
  })
58
208
 
59
209
  describe("POST /api/global/auth/:tenantId/reset/update", () => {
60
210
  it("should reset password", async () => {
211
+ let user = await config.createUser()
61
212
  const { code } = await config.api.auth.requestPasswordReset(
62
- sendMailMock
213
+ sendMailMock,
214
+ user.email
63
215
  )
64
- const user = await config.getUser("test@test.com")
65
216
  delete user.password
66
217
 
67
- const res = await config.api.auth.updatePassword(code)
218
+ const newPassword = "newpassword"
219
+ const res = await config.api.auth.updatePassword(code!, newPassword)
220
+
221
+ user = await config.getUser(user.email)
222
+ delete user.password
68
223
 
69
224
  expect(res.body).toEqual({ message: "password reset successfully." })
70
225
  expect(events.user.passwordReset).toBeCalledTimes(1)
71
226
  expect(events.user.passwordReset).toBeCalledWith(user)
72
227
 
73
- // TODO: Login using new password
228
+ // login using new password
229
+ await config.api.auth.login(user.tenantId, user.email, newPassword)
230
+ })
231
+
232
+ describe("sso user", () => {
233
+ let user: User | SSOUser
234
+
235
+ async function testSSOUser(code: string) {
236
+ const res = await config.api.auth.updatePassword(
237
+ code!,
238
+ generator.string(),
239
+ { status: 400 }
240
+ )
241
+
242
+ expect(res.body).toEqual({
243
+ message: "Cannot reset password.",
244
+ status: 400,
245
+ })
246
+ }
247
+
248
+ describe("budibase sso user", () => {
249
+ it("should prevent user from generating password reset email", async () => {
250
+ user = await config.createUser()
251
+ const { code } = await config.api.auth.requestPasswordReset(
252
+ sendMailMock,
253
+ user.email
254
+ )
255
+
256
+ // convert to sso now that password reset has been requested
257
+ const ssoUser = user as SSOUser
258
+ ssoUser.providerType = structures.sso.providerType()
259
+ delete ssoUser.password
260
+ await config.doInTenant(() => userSdk.save(ssoUser))
261
+
262
+ await testSSOUser(code!)
263
+ })
264
+ })
265
+
266
+ describe("root account sso user", () => {
267
+ it("should prevent user from generating password reset email", async () => {
268
+ user = await config.createUser()
269
+ const { code } = await config.api.auth.requestPasswordReset(
270
+ sendMailMock,
271
+ user.email
272
+ )
273
+
274
+ // convert to account owner now that password has been requested
275
+ const account = structures.accounts.ssoAccount() as CloudAccount
276
+ mocks.accounts.getAccount.mockReturnValueOnce(
277
+ Promise.resolve(account)
278
+ )
279
+
280
+ await testSSOUser(code!)
281
+ })
282
+ })
74
283
  })
75
284
  })
76
285
  })
@@ -145,7 +354,7 @@ describe("/api/global/auth", () => {
145
354
  const location: string = res.get("location")
146
355
  expect(
147
356
  location.startsWith(
148
- "http://localhost/auth?response_type=code&client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A10000%2Fapi%2Fglobal%2Fauth%2Fdefault%2Foidc%2Fcallback&scope=openid%20profile%20email%20offline_access"
357
+ `http://localhost/auth?response_type=code&client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A10000%2Fapi%2Fglobal%2Fauth%2F${config.tenantId}%2Foidc%2Fcallback&scope=openid%20profile%20email%20offline_access`
149
358
  )
150
359
  ).toBe(true)
151
360
  })