@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.
- package/__mocks__/node-fetch.ts +23 -0
- package/jest-testcontainers-config.js +8 -0
- package/jest.config.ts +3 -2
- package/package.json +10 -7
- package/scripts/dev/manage.js +1 -0
- package/src/api/controllers/global/auth.ts +77 -70
- package/src/api/controllers/global/self.ts +28 -44
- package/src/api/controllers/global/users.ts +65 -25
- 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/tests/auth.spec.ts +226 -17
- package/src/api/routes/global/tests/configs.spec.ts +5 -5
- 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 +24 -27
- 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/ddApm.ts +7 -0
- package/src/elasticApm.ts +10 -0
- package/src/environment.ts +15 -1
- package/src/index.ts +12 -7
- 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 +52 -0
- package/src/sdk/users/users.ts +53 -46
- package/src/tests/TestConfiguration.ts +41 -62
- package/src/tests/api/auth.ts +42 -14
- package/src/tests/api/base.ts +2 -1
- package/src/tests/api/configs.ts +2 -2
- 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/index.ts +0 -4
- package/src/utilities/email.ts +3 -3
- 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.isPreventSSOPasswords(user)) {
|
|
62
|
+
throw new HTTPError("SSO user cannot reset password", 400)
|
|
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,52 @@
|
|
|
1
|
+
import { structures } from "../../../tests"
|
|
2
|
+
import * as users from "../users"
|
|
3
|
+
import env from "../../../environment"
|
|
4
|
+
import { mocks } from "@budibase/backend-core/tests"
|
|
5
|
+
import { CloudAccount } from "@budibase/types"
|
|
6
|
+
|
|
7
|
+
describe("users", () => {
|
|
8
|
+
describe("isPreventSSOPasswords", () => {
|
|
9
|
+
it("returns true for sso account user", async () => {
|
|
10
|
+
const user = structures.users.user()
|
|
11
|
+
mocks.accounts.getAccount.mockReturnValue(
|
|
12
|
+
Promise.resolve(structures.accounts.ssoAccount() as CloudAccount)
|
|
13
|
+
)
|
|
14
|
+
const result = await users.isPreventSSOPasswords(user)
|
|
15
|
+
expect(result).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it("returns true for sso user", async () => {
|
|
19
|
+
const user = structures.users.ssoUser()
|
|
20
|
+
const result = await users.isPreventSSOPasswords(user)
|
|
21
|
+
expect(result).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe("sso maintenance mode", () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
env._set("ENABLE_SSO_MAINTENANCE_MODE", true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
env._set("ENABLE_SSO_MAINTENANCE_MODE", false)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe("non-admin user", () => {
|
|
34
|
+
it("returns true", async () => {
|
|
35
|
+
const user = structures.users.ssoUser()
|
|
36
|
+
const result = await users.isPreventSSOPasswords(user)
|
|
37
|
+
expect(result).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe("admin user", () => {
|
|
42
|
+
it("returns false", async () => {
|
|
43
|
+
const user = structures.users.ssoUser({
|
|
44
|
+
user: structures.users.adminUser(),
|
|
45
|
+
})
|
|
46
|
+
const result = await users.isPreventSSOPasswords(user)
|
|
47
|
+
expect(result).toBe(false)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
})
|
package/src/sdk/users/users.ts
CHANGED
|
@@ -6,12 +6,11 @@ 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,
|
|
@@ -21,21 +20,22 @@ import {
|
|
|
21
20
|
AllDocsResponse,
|
|
22
21
|
BulkUserResponse,
|
|
23
22
|
CloudAccount,
|
|
24
|
-
CreateUserResponse,
|
|
25
23
|
InviteUsersRequest,
|
|
26
24
|
InviteUsersResponse,
|
|
27
|
-
|
|
25
|
+
isSSOAccount,
|
|
26
|
+
isSSOUser,
|
|
28
27
|
PlatformUser,
|
|
29
28
|
PlatformUserByEmail,
|
|
30
29
|
RowResponse,
|
|
31
30
|
SearchUsersRequest,
|
|
31
|
+
UpdateSelf,
|
|
32
32
|
User,
|
|
33
|
-
|
|
34
|
-
isUser,
|
|
33
|
+
SaveUserOpts,
|
|
35
34
|
} from "@budibase/types"
|
|
36
35
|
import { sendEmail } from "../../utilities/email"
|
|
37
36
|
import { EmailTemplatePurpose } from "../../constants"
|
|
38
37
|
import { groups as groupsSdk } from "@budibase/pro"
|
|
38
|
+
import * as accountSdk from "../accounts"
|
|
39
39
|
|
|
40
40
|
const PAGE_LIMIT = 8
|
|
41
41
|
|
|
@@ -94,26 +94,23 @@ export const paginatedUsers = async ({
|
|
|
94
94
|
})
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
export async function getUserByEmail(email: string) {
|
|
98
|
+
return usersCore.getGlobalUserByEmail(email)
|
|
99
|
+
}
|
|
100
|
+
|
|
97
101
|
/**
|
|
98
102
|
* Gets a user by ID from the global database, based on the current tenancy.
|
|
99
103
|
*/
|
|
100
104
|
export const getUser = async (userId: string) => {
|
|
101
|
-
const
|
|
102
|
-
let user = await db.get(userId)
|
|
105
|
+
const user = await usersCore.getById(userId)
|
|
103
106
|
if (user) {
|
|
104
107
|
delete user.password
|
|
105
108
|
}
|
|
106
109
|
return user
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
export interface SaveUserOpts {
|
|
110
|
-
hashPassword?: boolean
|
|
111
|
-
requirePassword?: boolean
|
|
112
|
-
currentUserId?: string
|
|
113
|
-
}
|
|
114
|
-
|
|
115
112
|
const buildUser = async (
|
|
116
|
-
user: User
|
|
113
|
+
user: User,
|
|
117
114
|
opts: SaveUserOpts = {
|
|
118
115
|
hashPassword: true,
|
|
119
116
|
requirePassword: true,
|
|
@@ -121,11 +118,13 @@ const buildUser = async (
|
|
|
121
118
|
tenantId: string,
|
|
122
119
|
dbUser?: any
|
|
123
120
|
): Promise<User> => {
|
|
124
|
-
let
|
|
125
|
-
let { password, _id } = fullUser
|
|
121
|
+
let { password, _id } = user
|
|
126
122
|
|
|
127
123
|
let hashedPassword
|
|
128
124
|
if (password) {
|
|
125
|
+
if (await isPreventSSOPasswords(user)) {
|
|
126
|
+
throw new HTTPError("SSO user cannot set password", 400)
|
|
127
|
+
}
|
|
129
128
|
hashedPassword = opts.hashPassword ? await utils.hash(password) : password
|
|
130
129
|
} else if (dbUser) {
|
|
131
130
|
hashedPassword = dbUser.password
|
|
@@ -135,10 +134,10 @@ const buildUser = async (
|
|
|
135
134
|
|
|
136
135
|
_id = _id || dbUtils.generateGlobalUserID()
|
|
137
136
|
|
|
138
|
-
fullUser = {
|
|
137
|
+
const fullUser = {
|
|
139
138
|
createdAt: Date.now(),
|
|
140
139
|
...dbUser,
|
|
141
|
-
...
|
|
140
|
+
...user,
|
|
142
141
|
_id,
|
|
143
142
|
password: hashedPassword,
|
|
144
143
|
tenantId,
|
|
@@ -189,10 +188,36 @@ const validateUniqueUser = async (email: string, tenantId: string) => {
|
|
|
189
188
|
}
|
|
190
189
|
}
|
|
191
190
|
|
|
191
|
+
export async function isPreventSSOPasswords(user: User) {
|
|
192
|
+
// when in maintenance mode we allow sso users with the admin role
|
|
193
|
+
// to perform any password action - this prevents lockout
|
|
194
|
+
if (env.ENABLE_SSO_MAINTENANCE_MODE && user.admin?.global) {
|
|
195
|
+
return false
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check local sso
|
|
199
|
+
if (isSSOUser(user)) {
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Check account sso
|
|
204
|
+
const account = await accountSdk.api.getAccount(user.email)
|
|
205
|
+
return !!(account && isSSOAccount(account))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function updateSelf(id: string, data: UpdateSelf) {
|
|
209
|
+
let user = await getUser(id)
|
|
210
|
+
user = {
|
|
211
|
+
...user,
|
|
212
|
+
...data,
|
|
213
|
+
}
|
|
214
|
+
return save(user)
|
|
215
|
+
}
|
|
216
|
+
|
|
192
217
|
export const save = async (
|
|
193
|
-
user: User
|
|
218
|
+
user: User,
|
|
194
219
|
opts: SaveUserOpts = {}
|
|
195
|
-
): Promise<
|
|
220
|
+
): Promise<User> => {
|
|
196
221
|
// default booleans to true
|
|
197
222
|
if (opts.hashPassword == null) {
|
|
198
223
|
opts.hashPassword = true
|
|
@@ -264,7 +289,7 @@ export const save = async (
|
|
|
264
289
|
builtUser._rev = response.rev
|
|
265
290
|
|
|
266
291
|
await eventHelpers.handleSaveEvents(builtUser, dbUser)
|
|
267
|
-
await
|
|
292
|
+
await platform.users.addUser(tenantId, builtUser._id!, builtUser.email)
|
|
268
293
|
await cache.user.invalidateUser(response.id)
|
|
269
294
|
|
|
270
295
|
// let server know to sync user
|
|
@@ -272,11 +297,8 @@ export const save = async (
|
|
|
272
297
|
|
|
273
298
|
await Promise.all(groupPromises)
|
|
274
299
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
_rev: response.rev,
|
|
278
|
-
email,
|
|
279
|
-
}
|
|
300
|
+
// finally returned the saved user from the db
|
|
301
|
+
return db.get(builtUser._id!)
|
|
280
302
|
} catch (err: any) {
|
|
281
303
|
if (err.status === 409) {
|
|
282
304
|
throw "User exists already"
|
|
@@ -286,21 +308,6 @@ export const save = async (
|
|
|
286
308
|
}
|
|
287
309
|
}
|
|
288
310
|
|
|
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
311
|
const getExistingTenantUsers = async (emails: string[]): Promise<User[]> => {
|
|
305
312
|
const lcEmails = emails.map(email => email.toLowerCase())
|
|
306
313
|
const params = {
|
|
@@ -432,7 +439,7 @@ export const bulkCreate = async (
|
|
|
432
439
|
for (const user of usersToBulkSave) {
|
|
433
440
|
// TODO: Refactor to bulk insert users into the info db
|
|
434
441
|
// instead of relying on looping tenant creation
|
|
435
|
-
await
|
|
442
|
+
await platform.users.addUser(tenantId, user._id, user.email)
|
|
436
443
|
await eventHelpers.handleSaveEvents(user, undefined)
|
|
437
444
|
await apps.syncUserInApps(user._id)
|
|
438
445
|
}
|
|
@@ -550,7 +557,7 @@ export const bulkDelete = async (
|
|
|
550
557
|
|
|
551
558
|
export const destroy = async (id: string, currentUser: any) => {
|
|
552
559
|
const db = tenancy.getGlobalDB()
|
|
553
|
-
const dbUser = await db.get(id)
|
|
560
|
+
const dbUser = (await db.get(id)) as User
|
|
554
561
|
const userId = dbUser._id as string
|
|
555
562
|
|
|
556
563
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
|
@@ -566,7 +573,7 @@ export const destroy = async (id: string, currentUser: any) => {
|
|
|
566
573
|
}
|
|
567
574
|
}
|
|
568
575
|
|
|
569
|
-
await
|
|
576
|
+
await platform.users.removeUser(dbUser)
|
|
570
577
|
|
|
571
578
|
await db.remove(userId, dbUser._rev)
|
|
572
579
|
|
|
@@ -579,7 +586,7 @@ export const destroy = async (id: string, currentUser: any) => {
|
|
|
579
586
|
|
|
580
587
|
const bulkDeleteProcessing = async (dbUser: User) => {
|
|
581
588
|
const userId = dbUser._id as string
|
|
582
|
-
await
|
|
589
|
+
await platform.users.removeUser(dbUser)
|
|
583
590
|
await eventHelpers.handleDeleteEvents(dbUser)
|
|
584
591
|
await cache.user.invalidateUser(userId)
|
|
585
592
|
await sessions.invalidateSessions(userId, { reason: "bulk-deletion" })
|
|
@@ -15,39 +15,29 @@ const supertest = require("supertest")
|
|
|
15
15
|
import { Config } from "../constants"
|
|
16
16
|
import {
|
|
17
17
|
users,
|
|
18
|
-
|
|
18
|
+
context,
|
|
19
19
|
sessions,
|
|
20
20
|
auth,
|
|
21
21
|
constants,
|
|
22
22
|
env as coreEnv,
|
|
23
23
|
} from "@budibase/backend-core"
|
|
24
|
-
import structures, {
|
|
25
|
-
import {
|
|
24
|
+
import structures, { CSRF_TOKEN } from "./structures"
|
|
25
|
+
import { SaveUserResponse, User, AuthToken } from "@budibase/types"
|
|
26
26
|
import API from "./api"
|
|
27
27
|
|
|
28
|
-
enum Mode {
|
|
29
|
-
CLOUD = "cloud",
|
|
30
|
-
SELF = "self",
|
|
31
|
-
}
|
|
32
|
-
|
|
33
28
|
class TestConfiguration {
|
|
34
29
|
server: any
|
|
35
30
|
request: any
|
|
36
31
|
api: API
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
tenantId: string
|
|
33
|
+
user?: User
|
|
34
|
+
userPassword = "test"
|
|
39
35
|
|
|
40
|
-
constructor(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
) {
|
|
46
|
-
if (opts.mode === Mode.CLOUD) {
|
|
47
|
-
this.modeCloud()
|
|
48
|
-
} else if (opts.mode === Mode.SELF) {
|
|
49
|
-
this.modeSelf()
|
|
50
|
-
}
|
|
36
|
+
constructor(opts: { openServer: boolean } = { openServer: true }) {
|
|
37
|
+
// default to cloud hosting
|
|
38
|
+
this.cloudHosted()
|
|
39
|
+
|
|
40
|
+
this.tenantId = structures.tenant.id()
|
|
51
41
|
|
|
52
42
|
if (opts.openServer) {
|
|
53
43
|
env.PORT = "0" // random port
|
|
@@ -63,26 +53,19 @@ class TestConfiguration {
|
|
|
63
53
|
return this.request
|
|
64
54
|
}
|
|
65
55
|
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
setMultiTenancy = (value: boolean) => {
|
|
69
|
-
env._set("MULTI_TENANCY", value)
|
|
70
|
-
coreEnv._set("MULTI_TENANCY", value)
|
|
71
|
-
}
|
|
56
|
+
// HOSTING
|
|
72
57
|
|
|
73
58
|
setSelfHosted = (value: boolean) => {
|
|
74
59
|
env._set("SELF_HOSTED", value)
|
|
75
60
|
coreEnv._set("SELF_HOSTED", value)
|
|
76
61
|
}
|
|
77
62
|
|
|
78
|
-
|
|
63
|
+
cloudHosted = () => {
|
|
79
64
|
this.setSelfHosted(false)
|
|
80
|
-
this.setMultiTenancy(true)
|
|
81
65
|
}
|
|
82
66
|
|
|
83
|
-
|
|
67
|
+
selfHosted = () => {
|
|
84
68
|
this.setSelfHosted(true)
|
|
85
|
-
this.setMultiTenancy(false)
|
|
86
69
|
}
|
|
87
70
|
|
|
88
71
|
// UTILS
|
|
@@ -103,7 +86,7 @@ class TestConfiguration {
|
|
|
103
86
|
if (params) {
|
|
104
87
|
request.params = params
|
|
105
88
|
}
|
|
106
|
-
await
|
|
89
|
+
await context.doInTenant(this.getTenantId(), () => {
|
|
107
90
|
return controlFunc(request)
|
|
108
91
|
})
|
|
109
92
|
return request.body
|
|
@@ -112,13 +95,13 @@ class TestConfiguration {
|
|
|
112
95
|
// SETUP / TEARDOWN
|
|
113
96
|
|
|
114
97
|
async beforeAll() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
98
|
+
try {
|
|
99
|
+
await this.createDefaultUser()
|
|
100
|
+
await this.createSession(this.user!)
|
|
101
|
+
} catch (e: any) {
|
|
102
|
+
console.error(e)
|
|
103
|
+
throw new Error(e.message)
|
|
104
|
+
}
|
|
122
105
|
}
|
|
123
106
|
|
|
124
107
|
async afterAll() {
|
|
@@ -129,6 +112,12 @@ class TestConfiguration {
|
|
|
129
112
|
|
|
130
113
|
// TENANCY
|
|
131
114
|
|
|
115
|
+
doInTenant(task: any) {
|
|
116
|
+
return context.doInTenant(this.tenantId, () => {
|
|
117
|
+
return task()
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
132
121
|
createTenant = async (): Promise<User> => {
|
|
133
122
|
// create user / new tenant
|
|
134
123
|
const res = await this.api.users.createAdminUser()
|
|
@@ -150,9 +139,9 @@ class TestConfiguration {
|
|
|
150
139
|
|
|
151
140
|
getTenantId() {
|
|
152
141
|
try {
|
|
153
|
-
return
|
|
154
|
-
} catch (e
|
|
155
|
-
return
|
|
142
|
+
return context.getTenantId()
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return this.tenantId!
|
|
156
145
|
}
|
|
157
146
|
}
|
|
158
147
|
|
|
@@ -200,14 +189,11 @@ class TestConfiguration {
|
|
|
200
189
|
}
|
|
201
190
|
|
|
202
191
|
defaultHeaders() {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} else {
|
|
209
|
-
throw new Error("could not determine auth headers to use")
|
|
210
|
-
}
|
|
192
|
+
return this.authHeaders(this.user!)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
tenantIdHeaders() {
|
|
196
|
+
return { [constants.Header.TENANT_ID]: this.tenantId }
|
|
211
197
|
}
|
|
212
198
|
|
|
213
199
|
internalAPIHeaders() {
|
|
@@ -222,22 +208,15 @@ class TestConfiguration {
|
|
|
222
208
|
|
|
223
209
|
async createDefaultUser() {
|
|
224
210
|
const user = structures.users.adminUser({
|
|
225
|
-
|
|
226
|
-
password: "test",
|
|
211
|
+
password: this.userPassword,
|
|
227
212
|
})
|
|
228
|
-
this.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
async createTenant1User() {
|
|
232
|
-
const user = structures.users.adminUser({
|
|
233
|
-
email: "tenant1@test.com",
|
|
234
|
-
password: "test",
|
|
213
|
+
await context.doInTenant(this.tenantId!, async () => {
|
|
214
|
+
this.user = await this.createUser(user)
|
|
235
215
|
})
|
|
236
|
-
this.tenant1User = await this.createUser(user)
|
|
237
216
|
}
|
|
238
217
|
|
|
239
218
|
async getUser(email: string): Promise<User> {
|
|
240
|
-
return
|
|
219
|
+
return context.doInTenant(this.getTenantId(), () => {
|
|
241
220
|
return users.getGlobalUserByEmail(email)
|
|
242
221
|
})
|
|
243
222
|
}
|
|
@@ -247,7 +226,7 @@ class TestConfiguration {
|
|
|
247
226
|
user = structures.users.user()
|
|
248
227
|
}
|
|
249
228
|
const response = await this._req(user, null, controllers.users.save)
|
|
250
|
-
const body = response as
|
|
229
|
+
const body = response as SaveUserResponse
|
|
251
230
|
return this.getUser(body.email)
|
|
252
231
|
}
|
|
253
232
|
|