@budibase/worker 2.3.19 → 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.
- package/__mocks__/node-fetch.ts +23 -0
- package/jest-testcontainers-config.js +8 -0
- package/jest.config.ts +14 -18
- package/package.json +12 -8
- package/scripts/dev/manage.js +1 -0
- package/scripts/test.sh +12 -0
- package/src/api/controllers/global/auth.ts +109 -111
- package/src/api/controllers/global/configs.ts +150 -142
- package/src/api/controllers/global/self.ts +28 -44
- package/src/api/controllers/global/users.ts +211 -32
- package/src/api/controllers/system/accounts.ts +7 -5
- package/src/api/controllers/system/tenants.ts +4 -8
- package/src/api/index.ts +4 -20
- package/src/api/routes/global/auth.ts +10 -7
- package/src/api/routes/global/configs.ts +6 -12
- package/src/api/routes/global/tests/auditLogs.spec.ts +111 -0
- package/src/api/routes/global/tests/auth.spec.ts +217 -18
- package/src/api/routes/global/tests/configs.spec.ts +93 -65
- package/src/api/routes/global/tests/realEmail.spec.ts +1 -0
- package/src/api/routes/global/tests/roles.spec.ts +20 -14
- package/src/api/routes/global/tests/self.spec.ts +3 -4
- package/src/api/routes/global/tests/users.spec.ts +29 -28
- package/src/api/routes/global/users.ts +19 -14
- package/src/api/routes/index.ts +3 -0
- package/src/api/routes/system/tenants.ts +1 -1
- package/src/api/routes/system/tests/accounts.spec.ts +4 -4
- package/src/api/routes/system/tests/migrations.spec.ts +2 -2
- package/src/api/routes/system/tests/restore.spec.ts +2 -2
- package/src/api/routes/system/tests/status.spec.ts +5 -5
- package/src/db/index.ts +8 -2
- package/src/ddApm.ts +7 -0
- package/src/elasticApm.ts +10 -0
- package/src/environment.ts +4 -0
- package/src/index.ts +29 -8
- package/src/middleware/tests/tenancy.spec.ts +4 -4
- package/src/migrations/functions/globalInfoSyncUsers.ts +4 -3
- package/src/sdk/accounts/index.ts +2 -1
- package/src/sdk/accounts/{accounts.ts → metadata.ts} +0 -1
- package/src/sdk/auth/auth.ts +86 -0
- package/src/sdk/auth/index.ts +1 -0
- package/src/sdk/tenants/index.ts +1 -0
- package/src/sdk/tenants/tenants.ts +76 -0
- package/src/sdk/users/events.ts +4 -0
- package/src/sdk/users/index.ts +1 -0
- package/src/sdk/users/tests/users.spec.ts +76 -0
- package/src/sdk/users/users.ts +86 -53
- package/src/tests/TestConfiguration.ts +41 -62
- package/src/tests/api/auditLogs.ts +26 -0
- package/src/tests/api/auth.ts +44 -14
- package/src/tests/api/base.ts +2 -1
- package/src/tests/api/configs.ts +9 -1
- package/src/tests/api/email.ts +1 -0
- package/src/tests/api/index.ts +3 -0
- package/src/tests/api/restore.ts +1 -0
- package/src/tests/api/users.ts +2 -2
- package/src/tests/jestEnv.ts +2 -1
- package/src/tests/jestSetup.ts +10 -6
- package/src/tests/logging.ts +34 -0
- package/src/tests/structures/configs.ts +20 -13
- package/src/tests/structures/index.ts +0 -4
- package/src/utilities/email.ts +22 -68
- package/src/utilities/redis.ts +42 -2
- package/src/utilities/templates.ts +4 -9
- package/coverage/clover.xml +0 -2199
- package/coverage/coverage-final.json +0 -84
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -431
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -196
- package/coverage/lcov-report/src/api/controllers/global/auth.ts.html +0 -934
- package/coverage/lcov-report/src/api/controllers/global/configs.ts.html +0 -1348
- package/coverage/lcov-report/src/api/controllers/global/email.ts.html +0 -196
- package/coverage/lcov-report/src/api/controllers/global/events.ts.html +0 -136
- package/coverage/lcov-report/src/api/controllers/global/index.html +0 -251
- package/coverage/lcov-report/src/api/controllers/global/license.ts.html +0 -187
- package/coverage/lcov-report/src/api/controllers/global/roles.ts.html +0 -283
- package/coverage/lcov-report/src/api/controllers/global/self.ts.html +0 -577
- package/coverage/lcov-report/src/api/controllers/global/templates.ts.html +0 -316
- package/coverage/lcov-report/src/api/controllers/global/users.ts.html +0 -838
- package/coverage/lcov-report/src/api/controllers/global/workspaces.ts.html +0 -244
- package/coverage/lcov-report/src/api/controllers/system/accounts.ts.html +0 -148
- package/coverage/lcov-report/src/api/controllers/system/environment.ts.html +0 -124
- package/coverage/lcov-report/src/api/controllers/system/index.html +0 -191
- package/coverage/lcov-report/src/api/controllers/system/migrations.ts.html +0 -124
- package/coverage/lcov-report/src/api/controllers/system/restore.ts.html +0 -124
- package/coverage/lcov-report/src/api/controllers/system/status.ts.html +0 -133
- package/coverage/lcov-report/src/api/controllers/system/tenants.ts.html +0 -154
- package/coverage/lcov-report/src/api/index.html +0 -116
- package/coverage/lcov-report/src/api/index.ts.html +0 -595
- package/coverage/lcov-report/src/api/routes/global/auth.ts.html +0 -349
- package/coverage/lcov-report/src/api/routes/global/configs.ts.html +0 -457
- package/coverage/lcov-report/src/api/routes/global/email.ts.html +0 -193
- package/coverage/lcov-report/src/api/routes/global/events.ts.html +0 -109
- package/coverage/lcov-report/src/api/routes/global/index.html +0 -251
- package/coverage/lcov-report/src/api/routes/global/license.ts.html +0 -124
- package/coverage/lcov-report/src/api/routes/global/roles.ts.html +0 -133
- package/coverage/lcov-report/src/api/routes/global/self.ts.html +0 -139
- package/coverage/lcov-report/src/api/routes/global/templates.ts.html +0 -196
- package/coverage/lcov-report/src/api/routes/global/users.ts.html +0 -475
- package/coverage/lcov-report/src/api/routes/global/workspaces.ts.html +0 -196
- package/coverage/lcov-report/src/api/routes/index.html +0 -116
- package/coverage/lcov-report/src/api/routes/index.ts.html +0 -202
- package/coverage/lcov-report/src/api/routes/system/accounts.ts.html +0 -142
- package/coverage/lcov-report/src/api/routes/system/environment.ts.html +0 -109
- package/coverage/lcov-report/src/api/routes/system/index.html +0 -191
- package/coverage/lcov-report/src/api/routes/system/migrations.ts.html +0 -142
- package/coverage/lcov-report/src/api/routes/system/restore.ts.html +0 -109
- package/coverage/lcov-report/src/api/routes/system/status.ts.html +0 -109
- package/coverage/lcov-report/src/api/routes/system/tenants.ts.html +0 -124
- package/coverage/lcov-report/src/api/routes/validation/index.html +0 -131
- package/coverage/lcov-report/src/api/routes/validation/index.ts.html +0 -88
- package/coverage/lcov-report/src/api/routes/validation/users.ts.html +0 -235
- package/coverage/lcov-report/src/constants/index.html +0 -116
- package/coverage/lcov-report/src/constants/index.ts.html +0 -637
- package/coverage/lcov-report/src/constants/templates/index.html +0 -116
- package/coverage/lcov-report/src/constants/templates/index.ts.html +0 -316
- package/coverage/lcov-report/src/db/index.html +0 -116
- package/coverage/lcov-report/src/db/index.ts.html +0 -115
- package/coverage/lcov-report/src/environment.ts.html +0 -388
- package/coverage/lcov-report/src/index.html +0 -131
- package/coverage/lcov-report/src/index.ts.html +0 -394
- package/coverage/lcov-report/src/middleware/cloudRestricted.ts.html +0 -139
- package/coverage/lcov-report/src/middleware/index.html +0 -116
- package/coverage/lcov-report/src/migrations/functions/globalInfoSyncUsers.ts.html +0 -145
- package/coverage/lcov-report/src/migrations/functions/index.html +0 -116
- package/coverage/lcov-report/src/migrations/index.html +0 -116
- package/coverage/lcov-report/src/migrations/index.ts.html +0 -271
- package/coverage/lcov-report/src/sdk/accounts/accounts.ts.html +0 -262
- package/coverage/lcov-report/src/sdk/accounts/index.html +0 -131
- package/coverage/lcov-report/src/sdk/accounts/index.ts.html +0 -88
- package/coverage/lcov-report/src/sdk/index.html +0 -116
- package/coverage/lcov-report/src/sdk/index.ts.html +0 -106
- package/coverage/lcov-report/src/sdk/users/events.ts.html +0 -601
- package/coverage/lcov-report/src/sdk/users/index.html +0 -146
- package/coverage/lcov-report/src/sdk/users/index.ts.html +0 -88
- package/coverage/lcov-report/src/sdk/users/users.ts.html +0 -1999
- package/coverage/lcov-report/src/tests/TestConfiguration.ts.html +0 -1075
- package/coverage/lcov-report/src/tests/api/accounts.ts.html +0 -160
- package/coverage/lcov-report/src/tests/api/auth.ts.html +0 -220
- package/coverage/lcov-report/src/tests/api/base.ts.html +0 -133
- package/coverage/lcov-report/src/tests/api/configs.ts.html +0 -226
- package/coverage/lcov-report/src/tests/api/email.ts.html +0 -148
- package/coverage/lcov-report/src/tests/api/environment.ts.html +0 -130
- package/coverage/lcov-report/src/tests/api/groups.ts.html +0 -163
- package/coverage/lcov-report/src/tests/api/index.html +0 -356
- package/coverage/lcov-report/src/tests/api/index.ts.html +0 -238
- package/coverage/lcov-report/src/tests/api/license.ts.html +0 -136
- package/coverage/lcov-report/src/tests/api/migrations.ts.html +0 -151
- package/coverage/lcov-report/src/tests/api/restore.ts.html +0 -127
- package/coverage/lcov-report/src/tests/api/roles.ts.html +0 -181
- package/coverage/lcov-report/src/tests/api/self.ts.html +0 -163
- package/coverage/lcov-report/src/tests/api/status.ts.html +0 -121
- package/coverage/lcov-report/src/tests/api/templates.ts.html +0 -175
- package/coverage/lcov-report/src/tests/api/tenants.ts.html +0 -130
- package/coverage/lcov-report/src/tests/api/users.ts.html +0 -514
- package/coverage/lcov-report/src/tests/controllers.ts.html +0 -100
- package/coverage/lcov-report/src/tests/index.html +0 -146
- package/coverage/lcov-report/src/tests/index.ts.html +0 -103
- package/coverage/lcov-report/src/tests/mocks/email.ts.html +0 -115
- package/coverage/lcov-report/src/tests/mocks/index.html +0 -131
- package/coverage/lcov-report/src/tests/mocks/index.ts.html +0 -106
- package/coverage/lcov-report/src/tests/structures/configs.ts.html +0 -313
- package/coverage/lcov-report/src/tests/structures/groups.ts.html +0 -118
- package/coverage/lcov-report/src/tests/structures/index.html +0 -161
- package/coverage/lcov-report/src/tests/structures/index.ts.html +0 -151
- package/coverage/lcov-report/src/tests/structures/users.ts.html +0 -196
- package/coverage/lcov-report/src/utilities/appService.ts.html +0 -208
- package/coverage/lcov-report/src/utilities/email.ts.html +0 -850
- package/coverage/lcov-report/src/utilities/fileSystem.ts.html +0 -100
- package/coverage/lcov-report/src/utilities/index.html +0 -206
- package/coverage/lcov-report/src/utilities/index.ts.html +0 -112
- package/coverage/lcov-report/src/utilities/redis.ts.html +0 -424
- package/coverage/lcov-report/src/utilities/templates.ts.html +0 -232
- package/coverage/lcov-report/src/utilities/users.ts.html +0 -133
- package/coverage/lcov.info +0 -4187
- 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
|
+
}
|
package/src/sdk/users/events.ts
CHANGED
package/src/sdk/users/index.ts
CHANGED
|
@@ -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
|
+
})
|
package/src/sdk/users/users.ts
CHANGED
|
@@ -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
|
-
|
|
26
|
+
isSSOAccount,
|
|
27
|
+
isSSOUser,
|
|
28
28
|
PlatformUser,
|
|
29
29
|
PlatformUserByEmail,
|
|
30
30
|
RowResponse,
|
|
31
31
|
SearchUsersRequest,
|
|
32
|
+
UpdateSelf,
|
|
32
33
|
User,
|
|
33
|
-
|
|
34
|
-
isUser,
|
|
34
|
+
SaveUserOpts,
|
|
35
35
|
} from "@budibase/types"
|
|
36
36
|
import { sendEmail } from "../../utilities/email"
|
|
37
37
|
import { EmailTemplatePurpose } from "../../constants"
|
|
38
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
-
}
|
|
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
|
-
...
|
|
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
|
|
240
|
+
user: User,
|
|
194
241
|
opts: SaveUserOpts = {}
|
|
195
|
-
): Promise<
|
|
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(
|
|
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
|
|
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
|
-
|
|
276
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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({
|