@budibase/worker 2.3.17-alpha.4 → 2.3.17-alpha.6

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 (43) hide show
  1. package/package.json +8 -7
  2. package/scripts/dev/manage.js +1 -0
  3. package/src/api/controllers/global/auth.ts +77 -70
  4. package/src/api/controllers/global/self.ts +28 -44
  5. package/src/api/controllers/global/users.ts +65 -25
  6. package/src/api/controllers/system/accounts.ts +7 -5
  7. package/src/api/controllers/system/tenants.ts +4 -8
  8. package/src/api/index.ts +4 -20
  9. package/src/api/routes/global/auth.ts +10 -7
  10. package/src/api/routes/global/tests/auth.spec.ts +234 -33
  11. package/src/api/routes/global/tests/configs.spec.ts +93 -113
  12. package/src/api/routes/global/tests/roles.spec.ts +3 -2
  13. package/src/api/routes/global/tests/self.spec.ts +3 -4
  14. package/src/api/routes/global/tests/users.spec.ts +44 -46
  15. package/src/api/routes/system/tenants.ts +1 -1
  16. package/src/api/routes/system/tests/accounts.spec.ts +4 -4
  17. package/src/api/routes/system/tests/migrations.spec.ts +2 -2
  18. package/src/api/routes/system/tests/restore.spec.ts +2 -2
  19. package/src/api/routes/system/tests/status.spec.ts +5 -5
  20. package/src/environment.ts +15 -1
  21. package/src/index.ts +6 -0
  22. package/src/middleware/tests/tenancy.spec.ts +4 -4
  23. package/src/migrations/functions/globalInfoSyncUsers.ts +4 -3
  24. package/src/sdk/accounts/index.ts +2 -1
  25. package/src/sdk/accounts/{accounts.ts → metadata.ts} +0 -1
  26. package/src/sdk/auth/auth.ts +86 -0
  27. package/src/sdk/auth/index.ts +1 -0
  28. package/src/sdk/tenants/index.ts +1 -0
  29. package/src/sdk/tenants/tenants.ts +76 -0
  30. package/src/sdk/users/events.ts +4 -0
  31. package/src/sdk/users/index.ts +1 -0
  32. package/src/sdk/users/tests/users.spec.ts +52 -0
  33. package/src/sdk/users/users.ts +53 -46
  34. package/src/tests/TestConfiguration.ts +38 -89
  35. package/src/tests/api/auth.ts +42 -18
  36. package/src/tests/api/base.ts +2 -1
  37. package/src/tests/api/restore.ts +1 -0
  38. package/src/tests/jestEnv.ts +2 -1
  39. package/src/tests/jestSetup.ts +2 -5
  40. package/src/tests/logging.ts +34 -0
  41. package/src/tests/structures/index.ts +0 -2
  42. package/src/utilities/email.ts +3 -3
  43. package/src/tests/structures/users.ts +0 -37
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/worker",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.3.17-alpha.4",
4
+ "version": "2.3.17-alpha.6",
5
5
  "description": "Budibase background service",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "author": "Budibase",
37
37
  "license": "GPL-3.0",
38
38
  "dependencies": {
39
- "@budibase/backend-core": "2.3.17-alpha.4",
40
- "@budibase/pro": "2.3.17-alpha.3",
41
- "@budibase/string-templates": "2.3.17-alpha.4",
42
- "@budibase/types": "2.3.17-alpha.4",
39
+ "@budibase/backend-core": "2.3.17-alpha.6",
40
+ "@budibase/pro": "2.3.17-alpha.5",
41
+ "@budibase/string-templates": "2.3.17-alpha.6",
42
+ "@budibase/types": "2.3.17-alpha.6",
43
43
  "@koa/router": "8.0.8",
44
44
  "@sentry/node": "6.17.7",
45
45
  "@techpass/passport-openidconnect": "0.3.2",
@@ -74,7 +74,7 @@
74
74
  "@swc/core": "^1.3.25",
75
75
  "@swc/jest": "^0.2.24",
76
76
  "@trendyol/jest-testcontainers": "^2.1.1",
77
- "@types/jest": "26.0.23",
77
+ "@types/jest": "28.1.1",
78
78
  "@types/jsonwebtoken": "8.5.1",
79
79
  "@types/koa": "2.13.4",
80
80
  "@types/koa__router": "8.0.8",
@@ -82,6 +82,7 @@
82
82
  "@types/node-fetch": "2.6.1",
83
83
  "@types/pouchdb": "6.4.0",
84
84
  "@types/server-destroy": "1.0.1",
85
+ "@types/supertest": "2.0.12",
85
86
  "@types/uuid": "8.3.4",
86
87
  "@typescript-eslint/parser": "5.45.0",
87
88
  "copyfiles": "2.4.1",
@@ -99,5 +100,5 @@
99
100
  "typescript": "4.7.3",
100
101
  "update-dotenv": "1.1.1"
101
102
  },
102
- "gitHead": "4145478e3835309343700c2593247399255205b5"
103
+ "gitHead": "1e58397f7bc6fa6f7a39242a9a256af1fdd6db4f"
103
104
  }
@@ -29,6 +29,7 @@ async function init() {
29
29
  SERVICE: "worker-service",
30
30
  DEPLOYMENT_ENVIRONMENT: "development",
31
31
  TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR",
32
+ ENABLE_EMAIL_TEST_MODE: 1,
32
33
  }
33
34
  let envFile = ""
34
35
  Object.keys(envFileJson).forEach(key => {
@@ -1,49 +1,55 @@
1
1
  import {
2
- auth,
2
+ auth as authCore,
3
3
  constants,
4
4
  context,
5
5
  db as dbCore,
6
6
  events,
7
7
  tenancy,
8
- users as usersCore,
9
- utils,
8
+ utils as utilsCore,
10
9
  } from "@budibase/backend-core"
11
- import { EmailTemplatePurpose } from "../../../constants"
12
- import { isEmailConfigured, sendEmail } from "../../../utilities/email"
13
- import { checkResetPasswordCode } from "../../../utilities/redis"
10
+ import {
11
+ ConfigType,
12
+ User,
13
+ Ctx,
14
+ LoginRequest,
15
+ SSOUser,
16
+ PasswordResetRequest,
17
+ PasswordResetUpdateRequest,
18
+ } from "@budibase/types"
14
19
  import env from "../../../environment"
15
- import sdk from "../../../sdk"
16
- import { ConfigType, User } from "@budibase/types"
17
20
 
18
- const { setCookie, getCookie, clearCookie, hash, platformLogout } = utils
19
- const { Cookie, Header } = constants
20
- const { passport, ssoCallbackUrl, google, oidc } = auth
21
+ import * as authSdk from "../../../sdk/auth"
22
+ import * as userSdk from "../../../sdk/users"
21
23
 
22
- export async function googleCallbackUrl(config?: { callbackURL?: string }) {
23
- return ssoCallbackUrl(tenancy.getGlobalDB(), config, ConfigType.GOOGLE)
24
- }
24
+ const { Cookie, Header } = constants
25
+ const { passport, ssoCallbackUrl, google, oidc } = authCore
26
+ const { setCookie, getCookie, clearCookie } = utilsCore
25
27
 
26
- export async function oidcCallbackUrl(config?: { callbackURL?: string }) {
27
- return ssoCallbackUrl(tenancy.getGlobalDB(), config, ConfigType.OIDC)
28
- }
28
+ // LOGIN / LOGOUT
29
29
 
30
- async function authInternal(ctx: any, user: any, err: any = null, info = null) {
30
+ async function passportCallback(
31
+ ctx: Ctx,
32
+ user: User,
33
+ err: any = null,
34
+ info: { message: string } | null = null
35
+ ) {
31
36
  if (err) {
32
37
  console.error("Authentication error")
33
38
  console.error(err)
34
39
  console.trace(err)
35
40
  return ctx.throw(403, info ? info : "Unauthorized")
36
41
  }
37
-
38
42
  if (!user) {
39
43
  console.error("Authentication error - no user provided")
40
44
  return ctx.throw(403, info ? info : "Unauthorized")
41
45
  }
42
46
 
47
+ const token = await authSdk.loginUser(user)
48
+
43
49
  // set a cookie for browser access
44
- setCookie(ctx, user.token, Cookie.Auth, { sign: false })
50
+ setCookie(ctx, token, Cookie.Auth, { sign: false })
45
51
  // set the token in a header as well for APIs
46
- ctx.set(Header.TOKEN, user.token)
52
+ ctx.set(Header.TOKEN, token)
47
53
  // get rid of any app cookies on login
48
54
  // have to check test because this breaks cypress
49
55
  if (!env.isTest()) {
@@ -51,11 +57,18 @@ async function authInternal(ctx: any, user: any, err: any = null, info = null) {
51
57
  }
52
58
  }
53
59
 
54
- export const authenticate = async (ctx: any, next: any) => {
60
+ export const login = async (ctx: Ctx<LoginRequest>, next: any) => {
61
+ const email = ctx.request.body.username
62
+
63
+ const user = await userSdk.getUserByEmail(email)
64
+ if (user && (await userSdk.isPreventSSOPasswords(user))) {
65
+ ctx.throw(400, "SSO user cannot login using password")
66
+ }
67
+
55
68
  return passport.authenticate(
56
69
  "local",
57
70
  async (err: any, user: User, info: any) => {
58
- await authInternal(ctx, user, err, info)
71
+ await passportCallback(ctx, user, err, info)
59
72
  await context.identity.doInUserContext(user, async () => {
60
73
  await events.auth.login("local")
61
74
  })
@@ -64,6 +77,15 @@ export const authenticate = async (ctx: any, next: any) => {
64
77
  )(ctx, next)
65
78
  }
66
79
 
80
+ export const logout = async (ctx: any) => {
81
+ if (ctx.user && ctx.user._id) {
82
+ await authSdk.logout({ ctx, userId: ctx.user._id })
83
+ }
84
+ ctx.body = { message: "User logged out." }
85
+ }
86
+
87
+ // INIT
88
+
67
89
  export const setInitInfo = (ctx: any) => {
68
90
  const initInfo = ctx.request.body
69
91
  setCookie(ctx, initInfo, Cookie.Init)
@@ -79,32 +101,16 @@ export const getInitInfo = (ctx: any) => {
79
101
  }
80
102
  }
81
103
 
104
+ // PASSWORD MANAGEMENT
105
+
82
106
  /**
83
107
  * Reset the user password, used as part of a forgotten password flow.
84
108
  */
85
- export const reset = async (ctx: any) => {
109
+ export const reset = async (ctx: Ctx<PasswordResetRequest>) => {
86
110
  const { email } = ctx.request.body
87
- const configured = await isEmailConfigured()
88
- if (!configured) {
89
- ctx.throw(
90
- 400,
91
- "Please contact your platform administrator, SMTP is not configured."
92
- )
93
- }
94
- try {
95
- const user = (await usersCore.getGlobalUserByEmail(email)) as User
96
- // only if user exists, don't error though if they don't
97
- if (user) {
98
- await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
99
- user,
100
- subject: "{{ company }} platform password reset",
101
- })
102
- await events.user.passwordResetRequested(user)
103
- }
104
- } catch (err) {
105
- console.log(err)
106
- // don't throw any kind of error to the user, this might give away something
107
- }
111
+
112
+ await authSdk.reset(email)
113
+
108
114
  ctx.body = {
109
115
  message: "Please check your email for a reset link.",
110
116
  }
@@ -113,32 +119,21 @@ export const reset = async (ctx: any) => {
113
119
  /**
114
120
  * Perform the user password update if the provided reset code is valid.
115
121
  */
116
- export const resetUpdate = async (ctx: any) => {
122
+ export const resetUpdate = async (ctx: Ctx<PasswordResetUpdateRequest>) => {
117
123
  const { resetCode, password } = ctx.request.body
118
124
  try {
119
- const { userId } = await checkResetPasswordCode(resetCode)
120
- const db = tenancy.getGlobalDB()
121
- const user = await db.get(userId)
122
- user.password = await hash(password)
123
- await db.put(user)
125
+ await authSdk.resetUpdate(resetCode, password)
124
126
  ctx.body = {
125
127
  message: "password reset successfully.",
126
128
  }
127
- // remove password from the user before sending events
128
- delete user.password
129
- await events.user.passwordReset(user)
130
129
  } catch (err) {
131
- console.error(err)
130
+ console.warn(err)
131
+ // hide any details of the error for security
132
132
  ctx.throw(400, "Cannot reset password.")
133
133
  }
134
134
  }
135
135
 
136
- export const logout = async (ctx: any) => {
137
- if (ctx.user && ctx.user._id) {
138
- await platformLogout({ ctx, userId: ctx.user._id })
139
- }
140
- ctx.body = { message: "User logged out." }
141
- }
136
+ // DATASOURCE
142
137
 
143
138
  export const datasourcePreAuth = async (ctx: any, next: any) => {
144
139
  const provider = ctx.params.provider
@@ -166,6 +161,12 @@ export const datasourceAuth = async (ctx: any, next: any) => {
166
161
  return handler.postAuth(passport, ctx, next)
167
162
  }
168
163
 
164
+ // GOOGLE SSO
165
+
166
+ export async function googleCallbackUrl(config?: { callbackURL?: string }) {
167
+ return ssoCallbackUrl(tenancy.getGlobalDB(), config, ConfigType.GOOGLE)
168
+ }
169
+
169
170
  /**
170
171
  * The initial call that google authentication makes to take you to the google login screen.
171
172
  * On a successful login, you will be redirected to the googleAuth callback route.
@@ -181,7 +182,7 @@ export const googlePreAuth = async (ctx: any, next: any) => {
181
182
  const strategy = await google.strategyFactory(
182
183
  config,
183
184
  callbackUrl,
184
- sdk.users.save
185
+ userSdk.save
185
186
  )
186
187
 
187
188
  return passport.authenticate(strategy, {
@@ -191,7 +192,7 @@ export const googlePreAuth = async (ctx: any, next: any) => {
191
192
  })(ctx, next)
192
193
  }
193
194
 
194
- export const googleAuth = async (ctx: any, next: any) => {
195
+ export const googleCallback = async (ctx: any, next: any) => {
195
196
  const db = tenancy.getGlobalDB()
196
197
 
197
198
  const config = await dbCore.getScopedConfig(db, {
@@ -202,14 +203,14 @@ export const googleAuth = async (ctx: any, next: any) => {
202
203
  const strategy = await google.strategyFactory(
203
204
  config,
204
205
  callbackUrl,
205
- sdk.users.save
206
+ userSdk.save
206
207
  )
207
208
 
208
209
  return passport.authenticate(
209
210
  strategy,
210
211
  { successRedirect: "/", failureRedirect: "/error" },
211
- async (err: any, user: User, info: any) => {
212
- await authInternal(ctx, user, err, info)
212
+ async (err: any, user: SSOUser, info: any) => {
213
+ await passportCallback(ctx, user, err, info)
213
214
  await context.identity.doInUserContext(user, async () => {
214
215
  await events.auth.login("google-internal")
215
216
  })
@@ -218,6 +219,12 @@ export const googleAuth = async (ctx: any, next: any) => {
218
219
  )(ctx, next)
219
220
  }
220
221
 
222
+ // OIDC SSO
223
+
224
+ export async function oidcCallbackUrl(config?: { callbackURL?: string }) {
225
+ return ssoCallbackUrl(tenancy.getGlobalDB(), config, ConfigType.OIDC)
226
+ }
227
+
221
228
  export const oidcStrategyFactory = async (ctx: any, configId: any) => {
222
229
  const db = tenancy.getGlobalDB()
223
230
  const config = await dbCore.getScopedConfig(db, {
@@ -233,7 +240,7 @@ export const oidcStrategyFactory = async (ctx: any, configId: any) => {
233
240
  chosenConfig,
234
241
  callbackUrl
235
242
  )
236
- return oidc.strategyFactory(enrichedConfig, sdk.users.save)
243
+ return oidc.strategyFactory(enrichedConfig, userSdk.save)
237
244
  }
238
245
 
239
246
  /**
@@ -265,15 +272,15 @@ export const oidcPreAuth = async (ctx: any, next: any) => {
265
272
  })(ctx, next)
266
273
  }
267
274
 
268
- export const oidcAuth = async (ctx: any, next: any) => {
275
+ export const oidcCallback = async (ctx: any, next: any) => {
269
276
  const configId = getCookie(ctx, Cookie.OIDC_CONFIG)
270
277
  const strategy = await oidcStrategyFactory(ctx, configId)
271
278
 
272
279
  return passport.authenticate(
273
280
  strategy,
274
281
  { successRedirect: "/", failureRedirect: "/error" },
275
- async (err: any, user: any, info: any) => {
276
- await authInternal(ctx, user, err, info)
282
+ async (err: any, user: SSOUser, info: any) => {
283
+ await passportCallback(ctx, user, err, info)
277
284
  await context.identity.doInUserContext(user, async () => {
278
285
  await events.auth.login("oidc")
279
286
  })
@@ -1,18 +1,22 @@
1
- import sdk from "../../../sdk"
1
+ import * as userSdk from "../../../sdk/users"
2
2
  import {
3
- events,
4
3
  featureFlags,
5
4
  tenancy,
6
5
  constants,
7
6
  db as dbCore,
8
7
  utils,
9
- cache,
10
8
  encryption,
9
+ auth as authCore,
11
10
  } from "@budibase/backend-core"
12
11
  import env from "../../../environment"
13
12
  import { groups } from "@budibase/pro"
14
- const { hash, platformLogout, getCookie, clearCookie, newid } = utils
15
- const { user: userCache } = cache
13
+ import {
14
+ UpdateSelfRequest,
15
+ UpdateSelfResponse,
16
+ UpdateSelf,
17
+ UserCtx,
18
+ } from "@budibase/types"
19
+ const { getCookie, clearCookie, newid } = utils
16
20
 
17
21
  function newTestApiKey() {
18
22
  return env.ENCRYPTED_TEST_PUBLIC_API_KEY
@@ -93,17 +97,6 @@ const addSessionAttributesToUser = (ctx: any) => {
93
97
  ctx.body.csrfToken = ctx.user.csrfToken
94
98
  }
95
99
 
96
- const sanitiseUserUpdate = (ctx: any) => {
97
- const allowed = ["firstName", "lastName", "password", "forceResetPassword"]
98
- const resp: { [key: string]: any } = {}
99
- for (let [key, value] of Object.entries(ctx.request.body)) {
100
- if (allowed.includes(key)) {
101
- resp[key] = value
102
- }
103
- }
104
- return resp
105
- }
106
-
107
100
  export async function getSelf(ctx: any) {
108
101
  if (!ctx.user) {
109
102
  ctx.throw(403, "User not logged in")
@@ -116,7 +109,7 @@ export async function getSelf(ctx: any) {
116
109
  checkCurrentApp(ctx)
117
110
 
118
111
  // get the main body of the user
119
- const user = await sdk.users.getUser(userId)
112
+ const user = await userSdk.getUser(userId)
120
113
  ctx.body = await groups.enrichUserRolesFromGroups(user)
121
114
 
122
115
  // add the feature flags for this tenant
@@ -126,39 +119,30 @@ export async function getSelf(ctx: any) {
126
119
  addSessionAttributesToUser(ctx)
127
120
  }
128
121
 
129
- export async function updateSelf(ctx: any) {
130
- const db = tenancy.getGlobalDB()
131
- const user = await db.get(ctx.user._id)
132
- let passwordChange = false
133
-
134
- const userUpdateObj = sanitiseUserUpdate(ctx)
135
- if (userUpdateObj.password) {
136
- // changing password
137
- passwordChange = true
138
- userUpdateObj.password = await hash(userUpdateObj.password)
122
+ export async function updateSelf(
123
+ ctx: UserCtx<UpdateSelfRequest, UpdateSelfResponse>
124
+ ) {
125
+ const body = ctx.request.body
126
+ const update: UpdateSelf = {
127
+ firstName: body.firstName,
128
+ lastName: body.lastName,
129
+ password: body.password,
130
+ forceResetPassword: body.forceResetPassword,
131
+ }
132
+
133
+ const user = await userSdk.updateSelf(ctx.user._id!, update)
134
+
135
+ if (update.password) {
139
136
  // Log all other sessions out apart from the current one
140
- await platformLogout({
137
+ await authCore.platformLogout({
141
138
  ctx,
142
- userId: ctx.user._id,
139
+ userId: ctx.user._id!,
143
140
  keepActiveSession: true,
144
141
  })
145
142
  }
146
143
 
147
- const response = await db.put({
148
- ...user,
149
- ...userUpdateObj,
150
- })
151
- await userCache.invalidateUser(user._id)
152
144
  ctx.body = {
153
- _id: response.id,
154
- _rev: response.rev,
155
- }
156
-
157
- // remove the old password from the user before sending events
158
- user._rev = response.rev
159
- delete user.password
160
- await events.user.updated(user)
161
- if (passwordChange) {
162
- await events.user.passwordUpdated(user)
145
+ _id: user._id!,
146
+ _rev: user._rev!,
163
147
  }
164
148
  }
@@ -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