@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
@@ -0,0 +1,86 @@
1
+ import {
2
+ auth as authCore,
3
+ tenancy,
4
+ utils as coreUtils,
5
+ sessions,
6
+ events,
7
+ HTTPError,
8
+ } from "@budibase/backend-core"
9
+ import { PlatformLogoutOpts, User } from "@budibase/types"
10
+ import jwt from "jsonwebtoken"
11
+ import env from "../../environment"
12
+ import * as userSdk from "../users"
13
+ import * as emails from "../../utilities/email"
14
+ import * as redis from "../../utilities/redis"
15
+ import { EmailTemplatePurpose } from "../../constants"
16
+
17
+ // LOGIN / LOGOUT
18
+
19
+ export async function loginUser(user: User) {
20
+ const sessionId = coreUtils.newid()
21
+ const tenantId = tenancy.getTenantId()
22
+ await sessions.createASession(user._id!, { sessionId, tenantId })
23
+ const token = jwt.sign(
24
+ {
25
+ userId: user._id,
26
+ sessionId,
27
+ tenantId,
28
+ },
29
+ env.JWT_SECRET!
30
+ )
31
+ return token
32
+ }
33
+
34
+ export async function logout(opts: PlatformLogoutOpts) {
35
+ // TODO: This should be moved out of core and into worker only
36
+ // account-portal can call worker endpoint
37
+ return authCore.platformLogout(opts)
38
+ }
39
+
40
+ // PASSWORD MANAGEMENT
41
+
42
+ /**
43
+ * Reset the user password, used as part of a forgotten password flow.
44
+ */
45
+ export const reset = async (email: string) => {
46
+ const configured = await emails.isEmailConfigured()
47
+ if (!configured) {
48
+ throw new HTTPError(
49
+ "Please contact your platform administrator, SMTP is not configured.",
50
+ 400
51
+ )
52
+ }
53
+
54
+ const user = await userSdk.core.getGlobalUserByEmail(email)
55
+ // exit if user doesn't exist
56
+ if (!user) {
57
+ return
58
+ }
59
+
60
+ // exit if user has sso
61
+ if (await userSdk.isPreventPasswordActions(user)) {
62
+ return
63
+ }
64
+
65
+ // send password reset
66
+ await emails.sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
67
+ user,
68
+ subject: "{{ company }} platform password reset",
69
+ })
70
+ await events.user.passwordResetRequested(user)
71
+ }
72
+
73
+ /**
74
+ * Perform the user password update if the provided reset code is valid.
75
+ */
76
+ export const resetUpdate = async (resetCode: string, password: string) => {
77
+ const { userId } = await redis.checkResetPasswordCode(resetCode)
78
+
79
+ let user = await userSdk.getUser(userId)
80
+ user.password = password
81
+ user = await userSdk.save(user)
82
+
83
+ // remove password from the user before sending events
84
+ delete user.password
85
+ await events.user.passwordReset(user)
86
+ }
@@ -0,0 +1 @@
1
+ export * from "./auth"
@@ -0,0 +1 @@
1
+ export * from "./tenants"
@@ -0,0 +1,76 @@
1
+ import { App } from "@budibase/types"
2
+ import { tenancy, db as dbCore, platform } from "@budibase/backend-core"
3
+ import { quotas } from "@budibase/pro"
4
+
5
+ export async function deleteTenant(tenantId: string) {
6
+ await quotas.bustCache()
7
+ await platform.tenants.removeTenant(tenantId)
8
+ await removeTenantUsers(tenantId)
9
+ await removeTenantApps(tenantId)
10
+ await removeGlobalDB(tenantId)
11
+ }
12
+
13
+ async function removeGlobalDB(tenantId: string) {
14
+ try {
15
+ const db = tenancy.getTenantDB(tenantId)
16
+ await db.destroy()
17
+ } catch (err) {
18
+ console.error(`Error removing tenant ${tenantId} users from info db`, err)
19
+ throw err
20
+ }
21
+ }
22
+
23
+ async function removeTenantApps(tenantId: string) {
24
+ try {
25
+ const apps = (await dbCore.getAllApps({ all: true })) as App[]
26
+ const destroyPromises = apps.map(app => {
27
+ const db = dbCore.getDB(app.appId)
28
+ return db.destroy()
29
+ })
30
+ await Promise.allSettled(destroyPromises)
31
+ } catch (err) {
32
+ console.error(`Error removing tenant ${tenantId} apps`, err)
33
+ throw err
34
+ }
35
+ }
36
+
37
+ function getTenantUsers(tenantId: string) {
38
+ const db = tenancy.getTenantDB(tenantId)
39
+
40
+ return db.allDocs(
41
+ dbCore.getGlobalUserParams(null, {
42
+ include_docs: true,
43
+ })
44
+ )
45
+ }
46
+
47
+ async function removeTenantUsers(tenantId: string) {
48
+ try {
49
+ const allUsers = await getTenantUsers(tenantId)
50
+ const allEmails = allUsers.rows.map((row: any) => row.doc.email)
51
+
52
+ // get the id and email doc ids
53
+ let keys = allUsers.rows.map((row: any) => row.id)
54
+ keys = keys.concat(allEmails)
55
+
56
+ const platformDb = platform.getPlatformDB()
57
+
58
+ // retrieve the docs
59
+ const userDocs = await platformDb.allDocs({
60
+ keys,
61
+ include_docs: true,
62
+ })
63
+
64
+ // delete the docs
65
+ const toDelete = userDocs.rows.map((row: any) => {
66
+ return {
67
+ ...row.doc,
68
+ _deleted: true,
69
+ }
70
+ })
71
+ await platformDb.bulkDocs(toDelete)
72
+ } catch (err) {
73
+ console.error(`Error removing tenant ${tenantId} users from info db`, err)
74
+ throw err
75
+ }
76
+ }
@@ -84,6 +84,10 @@ export const handleSaveEvents = async (
84
84
  ) {
85
85
  await events.user.passwordForceReset(user)
86
86
  }
87
+
88
+ if (user.password !== existingUser.password) {
89
+ await events.user.passwordUpdated(user)
90
+ }
87
91
  } else {
88
92
  await events.user.created(user)
89
93
  }
@@ -1 +1,2 @@
1
1
  export * from "./users"
2
+ export { users as core } from "@budibase/backend-core"
@@ -0,0 +1,76 @@
1
+ import { structures } from "../../../tests"
2
+ import { mocks } from "@budibase/backend-core/tests"
3
+ import { env } from "@budibase/backend-core"
4
+ import * as users from "../users"
5
+ import { CloudAccount } from "@budibase/types"
6
+ import { isPreventPasswordActions } from "../users"
7
+
8
+ jest.mock("@budibase/pro")
9
+ import * as _pro from "@budibase/pro"
10
+ const pro = jest.mocked(_pro, true)
11
+
12
+ describe("users", () => {
13
+ beforeEach(() => {
14
+ jest.clearAllMocks()
15
+ })
16
+
17
+ describe("isPreventPasswordActions", () => {
18
+ it("returns false for non sso user", async () => {
19
+ const user = structures.users.user()
20
+ const result = await users.isPreventPasswordActions(user)
21
+ expect(result).toBe(false)
22
+ })
23
+
24
+ it("returns true for sso account user", async () => {
25
+ const user = structures.users.user()
26
+ mocks.accounts.getAccount.mockReturnValue(
27
+ Promise.resolve(structures.accounts.ssoAccount() as CloudAccount)
28
+ )
29
+ const result = await users.isPreventPasswordActions(user)
30
+ expect(result).toBe(true)
31
+ })
32
+
33
+ it("returns true for sso user", async () => {
34
+ const user = structures.users.ssoUser()
35
+ const result = await users.isPreventPasswordActions(user)
36
+ expect(result).toBe(true)
37
+ })
38
+
39
+ describe("enforced sso", () => {
40
+ it("returns true for all users when sso is enforced", async () => {
41
+ const user = structures.users.user()
42
+ pro.features.isSSOEnforced.mockReturnValue(Promise.resolve(true))
43
+ const result = await users.isPreventPasswordActions(user)
44
+ expect(result).toBe(true)
45
+ })
46
+ })
47
+
48
+ describe("sso maintenance mode", () => {
49
+ beforeEach(() => {
50
+ env._set("ENABLE_SSO_MAINTENANCE_MODE", true)
51
+ })
52
+
53
+ afterEach(() => {
54
+ env._set("ENABLE_SSO_MAINTENANCE_MODE", false)
55
+ })
56
+
57
+ describe("non-admin user", () => {
58
+ it("returns true", async () => {
59
+ const user = structures.users.ssoUser()
60
+ const result = await users.isPreventPasswordActions(user)
61
+ expect(result).toBe(true)
62
+ })
63
+ })
64
+
65
+ describe("admin user", () => {
66
+ it("returns false", async () => {
67
+ const user = structures.users.ssoUser({
68
+ user: structures.users.adminUser(),
69
+ })
70
+ const result = await users.isPreventPasswordActions(user)
71
+ expect(result).toBe(false)
72
+ })
73
+ })
74
+ })
75
+ })
76
+ })
@@ -6,36 +6,37 @@ import {
6
6
  cache,
7
7
  constants,
8
8
  db as dbUtils,
9
- deprovisioning,
10
9
  events,
11
10
  HTTPError,
12
- migrations,
13
11
  sessions,
14
12
  tenancy,
13
+ platform,
15
14
  users as usersCore,
16
15
  utils,
17
16
  ViewName,
17
+ env as coreEnv,
18
18
  } from "@budibase/backend-core"
19
19
  import {
20
20
  AccountMetadata,
21
21
  AllDocsResponse,
22
22
  BulkUserResponse,
23
23
  CloudAccount,
24
- CreateUserResponse,
25
24
  InviteUsersRequest,
26
25
  InviteUsersResponse,
27
- MigrationType,
26
+ isSSOAccount,
27
+ isSSOUser,
28
28
  PlatformUser,
29
29
  PlatformUserByEmail,
30
30
  RowResponse,
31
31
  SearchUsersRequest,
32
+ UpdateSelf,
32
33
  User,
33
- ThirdPartyUser,
34
- isUser,
34
+ SaveUserOpts,
35
35
  } from "@budibase/types"
36
36
  import { sendEmail } from "../../utilities/email"
37
37
  import { EmailTemplatePurpose } from "../../constants"
38
- import { groups as groupsSdk } from "@budibase/pro"
38
+ import * as pro from "@budibase/pro"
39
+ import * as accountSdk from "../accounts"
39
40
 
40
41
  const PAGE_LIMIT = 8
41
42
 
@@ -56,11 +57,22 @@ export const countUsersByApp = async (appId: string) => {
56
57
  }
57
58
  }
58
59
 
60
+ export const getUsersByAppAccess = async (appId?: string) => {
61
+ const opts: any = {
62
+ include_docs: true,
63
+ limit: 50,
64
+ }
65
+ let response: User[] = await usersCore.searchGlobalUsersByAppAccess(
66
+ appId,
67
+ opts
68
+ )
69
+ return response
70
+ }
71
+
59
72
  export const paginatedUsers = async ({
60
73
  page,
61
74
  email,
62
75
  appId,
63
- userIds,
64
76
  }: SearchUsersRequest = {}) => {
65
77
  const db = tenancy.getGlobalDB()
66
78
  // get one extra document, to have the next page
@@ -94,26 +106,23 @@ export const paginatedUsers = async ({
94
106
  })
95
107
  }
96
108
 
109
+ export async function getUserByEmail(email: string) {
110
+ return usersCore.getGlobalUserByEmail(email)
111
+ }
112
+
97
113
  /**
98
114
  * Gets a user by ID from the global database, based on the current tenancy.
99
115
  */
100
116
  export const getUser = async (userId: string) => {
101
- const db = tenancy.getGlobalDB()
102
- let user = await db.get(userId)
117
+ const user = await usersCore.getById(userId)
103
118
  if (user) {
104
119
  delete user.password
105
120
  }
106
121
  return user
107
122
  }
108
123
 
109
- export interface SaveUserOpts {
110
- hashPassword?: boolean
111
- requirePassword?: boolean
112
- currentUserId?: string
113
- }
114
-
115
124
  const buildUser = async (
116
- user: User | ThirdPartyUser,
125
+ user: User,
117
126
  opts: SaveUserOpts = {
118
127
  hashPassword: true,
119
128
  requirePassword: true,
@@ -121,24 +130,31 @@ const buildUser = async (
121
130
  tenantId: string,
122
131
  dbUser?: any
123
132
  ): Promise<User> => {
124
- let fullUser = user as User
125
- let { password, _id } = fullUser
133
+ let { password, _id } = user
126
134
 
127
135
  let hashedPassword
128
136
  if (password) {
137
+ if (await isPreventPasswordActions(user)) {
138
+ throw new HTTPError("Password change is disabled for this user", 400)
139
+ }
129
140
  hashedPassword = opts.hashPassword ? await utils.hash(password) : password
130
141
  } else if (dbUser) {
131
142
  hashedPassword = dbUser.password
132
- } else if (opts.requirePassword) {
143
+ }
144
+
145
+ // passwords are never required if sso is enforced
146
+ const requirePasswords =
147
+ opts.requirePassword && !(await pro.features.isSSOEnforced())
148
+ if (!hashedPassword && requirePasswords) {
133
149
  throw "Password must be specified."
134
150
  }
135
151
 
136
152
  _id = _id || dbUtils.generateGlobalUserID()
137
153
 
138
- fullUser = {
154
+ const fullUser = {
139
155
  createdAt: Date.now(),
140
156
  ...dbUser,
141
- ...fullUser,
157
+ ...user,
142
158
  _id,
143
159
  password: hashedPassword,
144
160
  tenantId,
@@ -189,10 +205,41 @@ const validateUniqueUser = async (email: string, tenantId: string) => {
189
205
  }
190
206
  }
191
207
 
208
+ export async function isPreventPasswordActions(user: User) {
209
+ // when in maintenance mode we allow sso users with the admin role
210
+ // to perform any password action - this prevents lockout
211
+ if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE && user.admin?.global) {
212
+ return false
213
+ }
214
+
215
+ // SSO is enforced for all users
216
+ if (await pro.features.isSSOEnforced()) {
217
+ return true
218
+ }
219
+
220
+ // Check local sso
221
+ if (isSSOUser(user)) {
222
+ return true
223
+ }
224
+
225
+ // Check account sso
226
+ const account = await accountSdk.api.getAccount(user.email)
227
+ return !!(account && isSSOAccount(account))
228
+ }
229
+
230
+ export async function updateSelf(id: string, data: UpdateSelf) {
231
+ let user = await getUser(id)
232
+ user = {
233
+ ...user,
234
+ ...data,
235
+ }
236
+ return save(user)
237
+ }
238
+
192
239
  export const save = async (
193
- user: User | ThirdPartyUser,
240
+ user: User,
194
241
  opts: SaveUserOpts = {}
195
- ): Promise<CreateUserResponse> => {
242
+ ): Promise<User> => {
196
243
  // default booleans to true
197
244
  if (opts.hashPassword == null) {
198
245
  opts.hashPassword = true
@@ -203,7 +250,7 @@ export const save = async (
203
250
  const tenantId = tenancy.getTenantId()
204
251
  const db = tenancy.getGlobalDB()
205
252
 
206
- let { email, _id, userGroups = [] } = user
253
+ let { email, _id, userGroups = [], roles } = user
207
254
 
208
255
  if (!email && !_id) {
209
256
  throw new Error("_id or email is required")
@@ -245,6 +292,10 @@ export const save = async (
245
292
  builtUser.roles = dbUser.roles
246
293
  }
247
294
 
295
+ if (!dbUser && roles?.length) {
296
+ builtUser.roles = { ...roles }
297
+ }
298
+
248
299
  // make sure we set the _id field for a new user
249
300
  // Also if this is a new user, associate groups with them
250
301
  let groupPromises = []
@@ -253,7 +304,7 @@ export const save = async (
253
304
 
254
305
  if (userGroups.length > 0) {
255
306
  for (let groupId of userGroups) {
256
- groupPromises.push(groupsSdk.addUsers(groupId, [_id]))
307
+ groupPromises.push(pro.groups.addUsers(groupId, [_id]))
257
308
  }
258
309
  }
259
310
  }
@@ -264,7 +315,7 @@ export const save = async (
264
315
  builtUser._rev = response.rev
265
316
 
266
317
  await eventHelpers.handleSaveEvents(builtUser, dbUser)
267
- await addTenant(tenantId, _id, email)
318
+ await platform.users.addUser(tenantId, builtUser._id!, builtUser.email)
268
319
  await cache.user.invalidateUser(response.id)
269
320
 
270
321
  // let server know to sync user
@@ -272,11 +323,8 @@ export const save = async (
272
323
 
273
324
  await Promise.all(groupPromises)
274
325
 
275
- return {
276
- _id: response.id,
277
- _rev: response.rev,
278
- email,
279
- }
326
+ // finally returned the saved user from the db
327
+ return db.get(builtUser._id!)
280
328
  } catch (err: any) {
281
329
  if (err.status === 409) {
282
330
  throw "User exists already"
@@ -286,21 +334,6 @@ export const save = async (
286
334
  }
287
335
  }
288
336
 
289
- export const addTenant = async (
290
- tenantId: string,
291
- _id: string,
292
- email: string
293
- ) => {
294
- if (env.MULTI_TENANCY) {
295
- const afterCreateTenant = () =>
296
- migrations.backPopulateMigrations({
297
- type: MigrationType.GLOBAL,
298
- tenantId,
299
- })
300
- await tenancy.tryAddTenant(tenantId, _id, email, afterCreateTenant)
301
- }
302
- }
303
-
304
337
  const getExistingTenantUsers = async (emails: string[]): Promise<User[]> => {
305
338
  const lcEmails = emails.map(email => email.toLowerCase())
306
339
  const params = {
@@ -432,7 +465,7 @@ export const bulkCreate = async (
432
465
  for (const user of usersToBulkSave) {
433
466
  // TODO: Refactor to bulk insert users into the info db
434
467
  // instead of relying on looping tenant creation
435
- await addTenant(tenantId, user._id, user.email)
468
+ await platform.users.addUser(tenantId, user._id, user.email)
436
469
  await eventHelpers.handleSaveEvents(user, undefined)
437
470
  await apps.syncUserInApps(user._id)
438
471
  }
@@ -449,7 +482,7 @@ export const bulkCreate = async (
449
482
  const groupPromises = []
450
483
  const createdUserIds = saved.map(user => user._id)
451
484
  for (let groupId of groups) {
452
- groupPromises.push(groupsSdk.addUsers(groupId, createdUserIds))
485
+ groupPromises.push(pro.groups.addUsers(groupId, createdUserIds))
453
486
  }
454
487
  await Promise.all(groupPromises)
455
488
  }
@@ -550,7 +583,7 @@ export const bulkDelete = async (
550
583
 
551
584
  export const destroy = async (id: string, currentUser: any) => {
552
585
  const db = tenancy.getGlobalDB()
553
- const dbUser = await db.get(id)
586
+ const dbUser = (await db.get(id)) as User
554
587
  const userId = dbUser._id as string
555
588
 
556
589
  if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
@@ -566,7 +599,7 @@ export const destroy = async (id: string, currentUser: any) => {
566
599
  }
567
600
  }
568
601
 
569
- await deprovisioning.removeUserFromInfoDB(dbUser)
602
+ await platform.users.removeUser(dbUser)
570
603
 
571
604
  await db.remove(userId, dbUser._rev)
572
605
 
@@ -579,7 +612,7 @@ export const destroy = async (id: string, currentUser: any) => {
579
612
 
580
613
  const bulkDeleteProcessing = async (dbUser: User) => {
581
614
  const userId = dbUser._id as string
582
- await deprovisioning.removeUserFromInfoDB(dbUser)
615
+ await platform.users.removeUser(dbUser)
583
616
  await eventHelpers.handleDeleteEvents(dbUser)
584
617
  await cache.user.invalidateUser(userId)
585
618
  await sessions.invalidateSessions(userId, { reason: "bulk-deletion" })
@@ -624,7 +657,7 @@ export const invite = async (
624
657
  }
625
658
  await sendEmail(user.email, EmailTemplatePurpose.INVITATION, opts)
626
659
  response.successful.push({ email: user.email })
627
- await events.user.invited()
660
+ await events.user.invited(user.email)
628
661
  } catch (e) {
629
662
  console.error(`Failed to send email invitation email=${user.email}`, e)
630
663
  response.unsuccessful.push({