@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.
- 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
|
@@ -1,31 +1,54 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
checkInviteCode,
|
|
3
|
+
getInviteCodes,
|
|
4
|
+
updateInviteCode,
|
|
5
|
+
} from "../../../utilities/redis"
|
|
6
|
+
// import sdk from "../../../sdk"
|
|
7
|
+
import * as userSdk from "../../../sdk/users"
|
|
3
8
|
import env from "../../../environment"
|
|
4
9
|
import {
|
|
10
|
+
AcceptUserInviteRequest,
|
|
11
|
+
AcceptUserInviteResponse,
|
|
5
12
|
BulkUserRequest,
|
|
6
13
|
BulkUserResponse,
|
|
7
14
|
CloudAccount,
|
|
8
15
|
CreateAdminUserRequest,
|
|
16
|
+
CreateAdminUserResponse,
|
|
17
|
+
Ctx,
|
|
9
18
|
InviteUserRequest,
|
|
10
19
|
InviteUsersRequest,
|
|
20
|
+
MigrationType,
|
|
21
|
+
SaveUserResponse,
|
|
11
22
|
SearchUsersRequest,
|
|
12
23
|
User,
|
|
24
|
+
UserCtx,
|
|
13
25
|
} from "@budibase/types"
|
|
14
26
|
import {
|
|
15
27
|
accounts,
|
|
16
28
|
cache,
|
|
17
29
|
errors,
|
|
18
30
|
events,
|
|
31
|
+
migrations,
|
|
19
32
|
tenancy,
|
|
33
|
+
platform,
|
|
20
34
|
} from "@budibase/backend-core"
|
|
21
35
|
import { checkAnyUserExists } from "../../../utilities/users"
|
|
36
|
+
import { isEmailConfigured } from "../../../utilities/email"
|
|
22
37
|
|
|
23
38
|
const MAX_USERS_UPLOAD_LIMIT = 1000
|
|
24
39
|
|
|
25
|
-
export const save = async (ctx:
|
|
40
|
+
export const save = async (ctx: UserCtx<User, SaveUserResponse>) => {
|
|
26
41
|
try {
|
|
27
42
|
const currentUserId = ctx.user._id
|
|
28
|
-
|
|
43
|
+
const requestUser = ctx.request.body
|
|
44
|
+
|
|
45
|
+
const user = await userSdk.save(requestUser, { currentUserId })
|
|
46
|
+
|
|
47
|
+
ctx.body = {
|
|
48
|
+
_id: user._id!,
|
|
49
|
+
_rev: user._rev!,
|
|
50
|
+
email: user.email,
|
|
51
|
+
}
|
|
29
52
|
} catch (err: any) {
|
|
30
53
|
ctx.throw(err.status || 400, err)
|
|
31
54
|
}
|
|
@@ -35,7 +58,7 @@ const bulkDelete = async (userIds: string[], currentUserId: string) => {
|
|
|
35
58
|
if (userIds?.indexOf(currentUserId) !== -1) {
|
|
36
59
|
throw new Error("Unable to delete self.")
|
|
37
60
|
}
|
|
38
|
-
return await
|
|
61
|
+
return await userSdk.bulkDelete(userIds)
|
|
39
62
|
}
|
|
40
63
|
|
|
41
64
|
const bulkCreate = async (users: User[], groupIds: string[]) => {
|
|
@@ -44,7 +67,7 @@ const bulkCreate = async (users: User[], groupIds: string[]) => {
|
|
|
44
67
|
"Max limit for upload is 1000 users. Please reduce file size and try again."
|
|
45
68
|
)
|
|
46
69
|
}
|
|
47
|
-
return await
|
|
70
|
+
return await userSdk.bulkCreate(users, groupIds)
|
|
48
71
|
}
|
|
49
72
|
|
|
50
73
|
export const bulkUpdate = async (ctx: any) => {
|
|
@@ -68,19 +91,30 @@ const parseBooleanParam = (param: any) => {
|
|
|
68
91
|
return !(param && param === "false")
|
|
69
92
|
}
|
|
70
93
|
|
|
71
|
-
export const adminUser = async (
|
|
72
|
-
|
|
73
|
-
|
|
94
|
+
export const adminUser = async (
|
|
95
|
+
ctx: Ctx<CreateAdminUserRequest, CreateAdminUserResponse>
|
|
96
|
+
) => {
|
|
97
|
+
const { email, password, tenantId } = ctx.request.body
|
|
98
|
+
|
|
99
|
+
if (await platform.tenants.exists(tenantId)) {
|
|
100
|
+
ctx.throw(403, "Organisation already exists.")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (env.MULTI_TENANCY) {
|
|
104
|
+
// store the new tenant record in the platform db
|
|
105
|
+
await platform.tenants.addTenant(tenantId)
|
|
106
|
+
await migrations.backPopulateMigrations({
|
|
107
|
+
type: MigrationType.GLOBAL,
|
|
108
|
+
tenantId,
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
74
112
|
await tenancy.doInTenant(tenantId, async () => {
|
|
75
113
|
// account portal sends a pre-hashed password - honour param to prevent double hashing
|
|
76
114
|
const hashPassword = parseBooleanParam(ctx.request.query.hashPassword)
|
|
77
115
|
// account portal sends no password for SSO users
|
|
78
116
|
const requirePassword = parseBooleanParam(ctx.request.query.requirePassword)
|
|
79
117
|
|
|
80
|
-
if (await tenancy.doesTenantExist(tenantId)) {
|
|
81
|
-
ctx.throw(403, "Organisation already exists.")
|
|
82
|
-
}
|
|
83
|
-
|
|
84
118
|
const userExists = await checkAnyUserExists()
|
|
85
119
|
if (userExists) {
|
|
86
120
|
ctx.throw(
|
|
@@ -106,7 +140,7 @@ export const adminUser = async (ctx: any) => {
|
|
|
106
140
|
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
|
107
141
|
// stuck in a cycle
|
|
108
142
|
await cache.bustCache(cache.CacheKey.CHECKLIST)
|
|
109
|
-
const finalUser = await
|
|
143
|
+
const finalUser = await userSdk.save(user, {
|
|
110
144
|
hashPassword,
|
|
111
145
|
requirePassword,
|
|
112
146
|
})
|
|
@@ -118,7 +152,11 @@ export const adminUser = async (ctx: any) => {
|
|
|
118
152
|
}
|
|
119
153
|
await events.identification.identifyTenantGroup(tenantId, account)
|
|
120
154
|
|
|
121
|
-
ctx.body =
|
|
155
|
+
ctx.body = {
|
|
156
|
+
_id: finalUser._id!,
|
|
157
|
+
_rev: finalUser._rev!,
|
|
158
|
+
email: finalUser.email,
|
|
159
|
+
}
|
|
122
160
|
} catch (err: any) {
|
|
123
161
|
ctx.throw(err.status || 400, err)
|
|
124
162
|
}
|
|
@@ -128,7 +166,7 @@ export const adminUser = async (ctx: any) => {
|
|
|
128
166
|
export const countByApp = async (ctx: any) => {
|
|
129
167
|
const appId = ctx.params.appId
|
|
130
168
|
try {
|
|
131
|
-
ctx.body = await
|
|
169
|
+
ctx.body = await userSdk.countUsersByApp(appId)
|
|
132
170
|
} catch (err: any) {
|
|
133
171
|
ctx.throw(err.status || 400, err)
|
|
134
172
|
}
|
|
@@ -140,28 +178,40 @@ export const destroy = async (ctx: any) => {
|
|
|
140
178
|
ctx.throw(400, "Unable to delete self.")
|
|
141
179
|
}
|
|
142
180
|
|
|
143
|
-
await
|
|
181
|
+
await userSdk.destroy(id, ctx.user)
|
|
144
182
|
|
|
145
183
|
ctx.body = {
|
|
146
184
|
message: `User ${id} deleted.`,
|
|
147
185
|
}
|
|
148
186
|
}
|
|
149
187
|
|
|
188
|
+
export const getAppUsers = async (ctx: any) => {
|
|
189
|
+
const body = ctx.request.body as SearchUsersRequest
|
|
190
|
+
const users = await userSdk.getUsersByAppAccess(body?.appId)
|
|
191
|
+
|
|
192
|
+
ctx.body = { data: users }
|
|
193
|
+
}
|
|
194
|
+
|
|
150
195
|
export const search = async (ctx: any) => {
|
|
151
196
|
const body = ctx.request.body as SearchUsersRequest
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
197
|
+
|
|
198
|
+
if (body.paginated === false) {
|
|
199
|
+
await getAppUsers(ctx)
|
|
200
|
+
} else {
|
|
201
|
+
const paginated = await userSdk.paginatedUsers(body)
|
|
202
|
+
// user hashed password shouldn't ever be returned
|
|
203
|
+
for (let user of paginated.data) {
|
|
204
|
+
if (user) {
|
|
205
|
+
delete user.password
|
|
206
|
+
}
|
|
157
207
|
}
|
|
208
|
+
ctx.body = paginated
|
|
158
209
|
}
|
|
159
|
-
ctx.body = paginated
|
|
160
210
|
}
|
|
161
211
|
|
|
162
212
|
// called internally by app server user fetch
|
|
163
213
|
export const fetch = async (ctx: any) => {
|
|
164
|
-
const all = await
|
|
214
|
+
const all = await userSdk.allUsers()
|
|
165
215
|
// user hashed password shouldn't ever be returned
|
|
166
216
|
for (let user of all) {
|
|
167
217
|
if (user) {
|
|
@@ -173,12 +223,12 @@ export const fetch = async (ctx: any) => {
|
|
|
173
223
|
|
|
174
224
|
// called internally by app server user find
|
|
175
225
|
export const find = async (ctx: any) => {
|
|
176
|
-
ctx.body = await
|
|
226
|
+
ctx.body = await userSdk.getUser(ctx.params.id)
|
|
177
227
|
}
|
|
178
228
|
|
|
179
229
|
export const tenantUserLookup = async (ctx: any) => {
|
|
180
230
|
const id = ctx.params.id
|
|
181
|
-
const user = await
|
|
231
|
+
const user = await userSdk.getPlatformUser(id)
|
|
182
232
|
if (user) {
|
|
183
233
|
ctx.body = user
|
|
184
234
|
} else {
|
|
@@ -186,9 +236,71 @@ export const tenantUserLookup = async (ctx: any) => {
|
|
|
186
236
|
}
|
|
187
237
|
}
|
|
188
238
|
|
|
239
|
+
/*
|
|
240
|
+
Encapsulate the app user onboarding flows here.
|
|
241
|
+
*/
|
|
242
|
+
export const onboardUsers = async (ctx: any) => {
|
|
243
|
+
const request = ctx.request.body as InviteUsersRequest | BulkUserRequest
|
|
244
|
+
const isBulkCreate = "create" in request
|
|
245
|
+
|
|
246
|
+
const emailConfigured = await isEmailConfigured()
|
|
247
|
+
|
|
248
|
+
let onboardingResponse
|
|
249
|
+
|
|
250
|
+
if (isBulkCreate) {
|
|
251
|
+
// @ts-ignore
|
|
252
|
+
const { users, groups, roles } = request.create
|
|
253
|
+
const assignUsers = users.map((user: User) => (user.roles = roles))
|
|
254
|
+
onboardingResponse = await userSdk.bulkCreate(assignUsers, groups)
|
|
255
|
+
ctx.body = onboardingResponse
|
|
256
|
+
} else if (emailConfigured) {
|
|
257
|
+
onboardingResponse = await inviteMultiple(ctx)
|
|
258
|
+
} else if (!emailConfigured) {
|
|
259
|
+
const inviteRequest = ctx.request.body as InviteUsersRequest
|
|
260
|
+
|
|
261
|
+
let createdPasswords: any = {}
|
|
262
|
+
|
|
263
|
+
const users: User[] = inviteRequest.map(invite => {
|
|
264
|
+
let password = Math.random().toString(36).substring(2, 22)
|
|
265
|
+
|
|
266
|
+
// Temp password to be passed to the user.
|
|
267
|
+
createdPasswords[invite.email] = password
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
email: invite.email,
|
|
271
|
+
password,
|
|
272
|
+
forceResetPassword: true,
|
|
273
|
+
roles: invite.userInfo.apps,
|
|
274
|
+
admin: { global: false },
|
|
275
|
+
builder: { global: false },
|
|
276
|
+
tenantId: tenancy.getTenantId(),
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
let bulkCreateReponse = await userSdk.bulkCreate(users, [])
|
|
280
|
+
|
|
281
|
+
// Apply temporary credentials
|
|
282
|
+
let createWithCredentials = {
|
|
283
|
+
...bulkCreateReponse,
|
|
284
|
+
successful: bulkCreateReponse?.successful.map(user => {
|
|
285
|
+
return {
|
|
286
|
+
...user,
|
|
287
|
+
password: createdPasswords[user.email],
|
|
288
|
+
}
|
|
289
|
+
}),
|
|
290
|
+
created: true,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
ctx.body = createWithCredentials
|
|
294
|
+
} else {
|
|
295
|
+
ctx.throw(400, "User onboarding failed")
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
189
299
|
export const invite = async (ctx: any) => {
|
|
190
300
|
const request = ctx.request.body as InviteUserRequest
|
|
191
|
-
|
|
301
|
+
|
|
302
|
+
let multiRequest = [request] as InviteUsersRequest
|
|
303
|
+
const response = await userSdk.invite(multiRequest)
|
|
192
304
|
|
|
193
305
|
// explicitly throw for single user invite
|
|
194
306
|
if (response.unsuccessful.length) {
|
|
@@ -202,12 +314,14 @@ export const invite = async (ctx: any) => {
|
|
|
202
314
|
|
|
203
315
|
ctx.body = {
|
|
204
316
|
message: "Invitation has been sent.",
|
|
317
|
+
successful: response.successful,
|
|
318
|
+
unsuccessful: response.unsuccessful,
|
|
205
319
|
}
|
|
206
320
|
}
|
|
207
321
|
|
|
208
322
|
export const inviteMultiple = async (ctx: any) => {
|
|
209
323
|
const request = ctx.request.body as InviteUsersRequest
|
|
210
|
-
ctx.body = await
|
|
324
|
+
ctx.body = await userSdk.invite(request)
|
|
211
325
|
}
|
|
212
326
|
|
|
213
327
|
export const checkInvite = async (ctx: any) => {
|
|
@@ -223,24 +337,89 @@ export const checkInvite = async (ctx: any) => {
|
|
|
223
337
|
}
|
|
224
338
|
}
|
|
225
339
|
|
|
226
|
-
export const
|
|
340
|
+
export const getUserInvites = async (ctx: any) => {
|
|
341
|
+
let invites
|
|
342
|
+
try {
|
|
343
|
+
// Restricted to the currently authenticated tenant
|
|
344
|
+
invites = await getInviteCodes([ctx.user.tenantId])
|
|
345
|
+
} catch (e) {
|
|
346
|
+
ctx.throw(400, "There was a problem fetching invites")
|
|
347
|
+
}
|
|
348
|
+
ctx.body = invites
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export const updateInvite = async (ctx: any) => {
|
|
352
|
+
const { code } = ctx.params
|
|
353
|
+
let updateBody = { ...ctx.request.body }
|
|
354
|
+
|
|
355
|
+
delete updateBody.email
|
|
356
|
+
|
|
357
|
+
let invite
|
|
358
|
+
try {
|
|
359
|
+
invite = await checkInviteCode(code, false)
|
|
360
|
+
if (!invite) {
|
|
361
|
+
throw new Error("The invite could not be retrieved")
|
|
362
|
+
}
|
|
363
|
+
} catch (e) {
|
|
364
|
+
ctx.throw(400, "There was a problem with the invite")
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let updated = {
|
|
368
|
+
...invite,
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (!updateBody?.apps || !Object.keys(updateBody?.apps).length) {
|
|
372
|
+
updated.info.apps = []
|
|
373
|
+
} else {
|
|
374
|
+
updated.info = {
|
|
375
|
+
...invite.info,
|
|
376
|
+
apps: {
|
|
377
|
+
...invite.info.apps,
|
|
378
|
+
...updateBody.apps,
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
await updateInviteCode(code, updated)
|
|
384
|
+
ctx.body = { ...invite }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export const inviteAccept = async (
|
|
388
|
+
ctx: Ctx<AcceptUserInviteRequest, AcceptUserInviteResponse>
|
|
389
|
+
) => {
|
|
227
390
|
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
|
228
391
|
try {
|
|
229
392
|
// info is an extension of the user object that was stored by global
|
|
230
393
|
const { email, info }: any = await checkInviteCode(inviteCode)
|
|
231
|
-
|
|
232
|
-
|
|
394
|
+
const user = await tenancy.doInTenant(info.tenantId, async () => {
|
|
395
|
+
let request = {
|
|
233
396
|
firstName,
|
|
234
397
|
lastName,
|
|
235
398
|
password,
|
|
236
399
|
email,
|
|
400
|
+
roles: info.apps,
|
|
401
|
+
tenantId: info.tenantId,
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
delete info.apps
|
|
405
|
+
|
|
406
|
+
request = {
|
|
407
|
+
...request,
|
|
237
408
|
...info,
|
|
238
|
-
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const saved = await userSdk.save(request)
|
|
239
412
|
const db = tenancy.getGlobalDB()
|
|
240
413
|
const user = await db.get(saved._id)
|
|
241
414
|
await events.user.inviteAccepted(user)
|
|
242
415
|
return saved
|
|
243
416
|
})
|
|
417
|
+
|
|
418
|
+
ctx.body = {
|
|
419
|
+
_id: user._id,
|
|
420
|
+
_rev: user._rev,
|
|
421
|
+
email: user.email,
|
|
422
|
+
}
|
|
244
423
|
} catch (err: any) {
|
|
245
424
|
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
|
246
425
|
// explicitly re-throw limit exceeded errors
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { Account, AccountMetadata } from "@budibase/types"
|
|
2
|
-
import
|
|
2
|
+
import * as accounts from "../../../sdk/accounts"
|
|
3
3
|
|
|
4
4
|
export const save = async (ctx: any) => {
|
|
5
5
|
const account = ctx.request.body as Account
|
|
6
6
|
let metadata: AccountMetadata = {
|
|
7
|
-
_id:
|
|
7
|
+
_id: accounts.metadata.formatAccountMetadataId(account.accountId),
|
|
8
8
|
email: account.email,
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
metadata = await
|
|
11
|
+
metadata = await accounts.metadata.saveMetadata(metadata)
|
|
12
12
|
|
|
13
13
|
ctx.body = metadata
|
|
14
14
|
ctx.status = 200
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export const destroy = async (ctx: any) => {
|
|
18
|
-
const accountId =
|
|
19
|
-
|
|
18
|
+
const accountId = accounts.metadata.formatAccountMetadataId(
|
|
19
|
+
ctx.params.accountId
|
|
20
|
+
)
|
|
21
|
+
await accounts.metadata.destroyMetadata(accountId)
|
|
20
22
|
ctx.status = 204
|
|
21
23
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { quotas } from "@budibase/pro"
|
|
1
|
+
import { UserCtx } from "@budibase/types"
|
|
2
|
+
import * as tenantSdk from "../../../sdk/tenants"
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
export async function destroy(ctx: UserCtx) {
|
|
6
5
|
const user = ctx.user!
|
|
7
6
|
const tenantId = ctx.params.tenantId
|
|
8
7
|
|
|
@@ -11,13 +10,10 @@ const _delete = async (ctx: BBContext) => {
|
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
try {
|
|
14
|
-
await
|
|
15
|
-
await deprovisioning.deleteTenant(tenantId)
|
|
13
|
+
await tenantSdk.deleteTenant(tenantId)
|
|
16
14
|
ctx.status = 204
|
|
17
15
|
} catch (err) {
|
|
18
16
|
ctx.log.error(err)
|
|
19
17
|
throw err
|
|
20
18
|
}
|
|
21
19
|
}
|
|
22
|
-
|
|
23
|
-
export { _delete as delete }
|
package/src/api/index.ts
CHANGED
|
@@ -3,8 +3,7 @@ const compress = require("koa-compress")
|
|
|
3
3
|
const zlib = require("zlib")
|
|
4
4
|
import { routes } from "./routes"
|
|
5
5
|
import { middleware as pro } from "@budibase/pro"
|
|
6
|
-
import {
|
|
7
|
-
import { APIError } from "@budibase/types"
|
|
6
|
+
import { auth, middleware } from "@budibase/backend-core"
|
|
8
7
|
|
|
9
8
|
const PUBLIC_ENDPOINTS = [
|
|
10
9
|
// deprecated single tenant sso callback
|
|
@@ -109,7 +108,9 @@ const NO_TENANCY_ENDPOINTS = [
|
|
|
109
108
|
const NO_CSRF_ENDPOINTS = [...PUBLIC_ENDPOINTS]
|
|
110
109
|
|
|
111
110
|
const router: Router = new Router()
|
|
111
|
+
|
|
112
112
|
router
|
|
113
|
+
.use(middleware.errorHandling)
|
|
113
114
|
.use(
|
|
114
115
|
compress({
|
|
115
116
|
threshold: 2048,
|
|
@@ -136,29 +137,12 @@ router
|
|
|
136
137
|
(!ctx.isAuthenticated || (ctx.user && !ctx.user.budibaseAccess)) &&
|
|
137
138
|
!ctx.internal
|
|
138
139
|
) {
|
|
139
|
-
ctx.throw(403, "Unauthorized
|
|
140
|
+
ctx.throw(403, "Unauthorized")
|
|
140
141
|
}
|
|
141
142
|
return next()
|
|
142
143
|
})
|
|
143
144
|
.use(middleware.auditLog)
|
|
144
145
|
|
|
145
|
-
// error handling middleware - TODO: This could be moved to backend-core
|
|
146
|
-
router.use(async (ctx, next) => {
|
|
147
|
-
try {
|
|
148
|
-
await next()
|
|
149
|
-
} catch (err: any) {
|
|
150
|
-
ctx.log.error(err)
|
|
151
|
-
ctx.status = err.status || err.statusCode || 500
|
|
152
|
-
const error = errors.getPublicError(err)
|
|
153
|
-
const body: APIError = {
|
|
154
|
-
message: err.message,
|
|
155
|
-
status: ctx.status,
|
|
156
|
-
error,
|
|
157
|
-
}
|
|
158
|
-
ctx.body = body
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
|
|
162
146
|
router.get("/health", ctx => (ctx.status = 200))
|
|
163
147
|
|
|
164
148
|
// authenticated routes
|
|
@@ -33,7 +33,7 @@ router
|
|
|
33
33
|
.post(
|
|
34
34
|
"/api/global/auth/:tenantId/login",
|
|
35
35
|
buildAuthValidation(),
|
|
36
|
-
authController.
|
|
36
|
+
authController.login
|
|
37
37
|
)
|
|
38
38
|
.post("/api/global/auth/logout", authController.logout)
|
|
39
39
|
.post(
|
|
@@ -68,21 +68,24 @@ router
|
|
|
68
68
|
|
|
69
69
|
// GOOGLE - MULTI TENANT
|
|
70
70
|
.get("/api/global/auth/:tenantId/google", authController.googlePreAuth)
|
|
71
|
-
.get(
|
|
71
|
+
.get(
|
|
72
|
+
"/api/global/auth/:tenantId/google/callback",
|
|
73
|
+
authController.googleCallback
|
|
74
|
+
)
|
|
72
75
|
|
|
73
76
|
// GOOGLE - SINGLE TENANT - DEPRECATED
|
|
74
|
-
.get("/api/global/auth/google/callback", authController.
|
|
75
|
-
.get("/api/admin/auth/google/callback", authController.
|
|
77
|
+
.get("/api/global/auth/google/callback", authController.googleCallback)
|
|
78
|
+
.get("/api/admin/auth/google/callback", authController.googleCallback)
|
|
76
79
|
|
|
77
80
|
// OIDC - MULTI TENANT
|
|
78
81
|
.get(
|
|
79
82
|
"/api/global/auth/:tenantId/oidc/configs/:configId",
|
|
80
83
|
authController.oidcPreAuth
|
|
81
84
|
)
|
|
82
|
-
.get("/api/global/auth/:tenantId/oidc/callback", authController.
|
|
85
|
+
.get("/api/global/auth/:tenantId/oidc/callback", authController.oidcCallback)
|
|
83
86
|
|
|
84
87
|
// OIDC - SINGLE TENANT - DEPRECATED
|
|
85
|
-
.get("/api/global/auth/oidc/callback", authController.
|
|
86
|
-
.get("/api/admin/auth/oidc/callback", authController.
|
|
88
|
+
.get("/api/global/auth/oidc/callback", authController.oidcCallback)
|
|
89
|
+
.get("/api/admin/auth/oidc/callback", authController.oidcCallback)
|
|
87
90
|
|
|
88
91
|
export default router
|
|
@@ -34,8 +34,8 @@ function settingValidation() {
|
|
|
34
34
|
function googleValidation() {
|
|
35
35
|
// prettier-ignore
|
|
36
36
|
return Joi.object({
|
|
37
|
-
clientID: Joi.
|
|
38
|
-
clientSecret: Joi.
|
|
37
|
+
clientID: Joi.string().required(),
|
|
38
|
+
clientSecret: Joi.string().required(),
|
|
39
39
|
activated: Joi.boolean().required(),
|
|
40
40
|
}).unknown(true)
|
|
41
41
|
}
|
|
@@ -45,12 +45,12 @@ function oidcValidation() {
|
|
|
45
45
|
return Joi.object({
|
|
46
46
|
configs: Joi.array().items(
|
|
47
47
|
Joi.object({
|
|
48
|
-
clientID: Joi.
|
|
49
|
-
clientSecret: Joi.
|
|
50
|
-
configUrl: Joi.
|
|
48
|
+
clientID: Joi.string().required(),
|
|
49
|
+
clientSecret: Joi.string().required(),
|
|
50
|
+
configUrl: Joi.string().required(),
|
|
51
51
|
logo: Joi.string().allow("", null),
|
|
52
52
|
name: Joi.string().allow("", null),
|
|
53
|
-
uuid: Joi.
|
|
53
|
+
uuid: Joi.string().required(),
|
|
54
54
|
activated: Joi.boolean().required(),
|
|
55
55
|
scopes: Joi.array().optional()
|
|
56
56
|
})
|
|
@@ -104,13 +104,7 @@ router
|
|
|
104
104
|
controller.save
|
|
105
105
|
)
|
|
106
106
|
.delete("/api/global/configs/:id/:rev", auth.adminOnly, controller.destroy)
|
|
107
|
-
.get("/api/global/configs", controller.fetch)
|
|
108
107
|
.get("/api/global/configs/checklist", controller.configChecklist)
|
|
109
|
-
.get(
|
|
110
|
-
"/api/global/configs/all/:type",
|
|
111
|
-
buildConfigGetValidation(),
|
|
112
|
-
controller.fetch
|
|
113
|
-
)
|
|
114
108
|
.get("/api/global/configs/public", controller.publicSettings)
|
|
115
109
|
.get("/api/global/configs/public/oidc", controller.publicOidc)
|
|
116
110
|
.get("/api/global/configs/:type", buildConfigGetValidation(), controller.find)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { mocks, structures } from "@budibase/backend-core/tests"
|
|
2
|
+
import { context, events } from "@budibase/backend-core"
|
|
3
|
+
import { Event, IdentityType } from "@budibase/types"
|
|
4
|
+
import { TestConfiguration } from "../../../../tests"
|
|
5
|
+
|
|
6
|
+
mocks.licenses.useAuditLogs()
|
|
7
|
+
|
|
8
|
+
const BASE_IDENTITY = {
|
|
9
|
+
account: undefined,
|
|
10
|
+
type: IdentityType.USER,
|
|
11
|
+
}
|
|
12
|
+
const USER_AUDIT_LOG_COUNT = 3
|
|
13
|
+
const APP_ID = "app_1"
|
|
14
|
+
|
|
15
|
+
describe("/api/global/auditlogs", () => {
|
|
16
|
+
const config = new TestConfiguration()
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
await config.beforeAll()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await config.afterAll()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe("POST /api/global/auditlogs/search", () => {
|
|
27
|
+
it("should be able to fire some events (create audit logs)", async () => {
|
|
28
|
+
await context.doInTenant(config.tenantId, async () => {
|
|
29
|
+
const userId = config.user!._id!
|
|
30
|
+
const identity = {
|
|
31
|
+
...BASE_IDENTITY,
|
|
32
|
+
_id: userId,
|
|
33
|
+
tenantId: config.tenantId,
|
|
34
|
+
}
|
|
35
|
+
await context.doInIdentityContext(identity, async () => {
|
|
36
|
+
for (let i = 0; i < USER_AUDIT_LOG_COUNT; i++) {
|
|
37
|
+
await events.user.created(structures.users.user())
|
|
38
|
+
}
|
|
39
|
+
await context.doInAppContext(APP_ID, async () => {
|
|
40
|
+
await events.app.created(structures.apps.app(APP_ID))
|
|
41
|
+
})
|
|
42
|
+
// fetch the user created events
|
|
43
|
+
const response = await config.api.auditLogs.search({
|
|
44
|
+
events: [Event.USER_CREATED],
|
|
45
|
+
})
|
|
46
|
+
expect(response.data).toBeDefined()
|
|
47
|
+
// there will be an initial event which comes from the default user creation
|
|
48
|
+
expect(response.data.length).toBe(USER_AUDIT_LOG_COUNT + 1)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("should be able to search by event", async () => {
|
|
54
|
+
const response = await config.api.auditLogs.search({
|
|
55
|
+
events: [Event.USER_CREATED],
|
|
56
|
+
})
|
|
57
|
+
expect(response.data.length).toBeGreaterThan(0)
|
|
58
|
+
for (let log of response.data) {
|
|
59
|
+
expect(log.event).toBe(Event.USER_CREATED)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("should be able to search by time range (frozen)", async () => {
|
|
64
|
+
// this is frozen, only need to add 1 and minus 1
|
|
65
|
+
const now = new Date()
|
|
66
|
+
const start = new Date()
|
|
67
|
+
start.setSeconds(now.getSeconds() - 1)
|
|
68
|
+
const end = new Date()
|
|
69
|
+
end.setSeconds(now.getSeconds() + 1)
|
|
70
|
+
const response = await config.api.auditLogs.search({
|
|
71
|
+
startDate: start.toISOString(),
|
|
72
|
+
endDate: end.toISOString(),
|
|
73
|
+
})
|
|
74
|
+
expect(response.data.length).toBeGreaterThan(0)
|
|
75
|
+
for (let log of response.data) {
|
|
76
|
+
expect(log.timestamp).toBe(now.toISOString())
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("should be able to search by user ID", async () => {
|
|
81
|
+
const userId = config.user!._id!
|
|
82
|
+
const response = await config.api.auditLogs.search({
|
|
83
|
+
userIds: [userId],
|
|
84
|
+
})
|
|
85
|
+
expect(response.data.length).toBeGreaterThan(0)
|
|
86
|
+
for (let log of response.data) {
|
|
87
|
+
expect(log.user._id).toBe(userId)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("should be able to search by app ID", async () => {
|
|
92
|
+
const response = await config.api.auditLogs.search({
|
|
93
|
+
appIds: [APP_ID],
|
|
94
|
+
})
|
|
95
|
+
expect(response.data.length).toBeGreaterThan(0)
|
|
96
|
+
for (let log of response.data) {
|
|
97
|
+
expect(log.app?._id).toBe(APP_ID)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it("should be able to search by full string", async () => {
|
|
102
|
+
const response = await config.api.auditLogs.search({
|
|
103
|
+
fullSearch: "User",
|
|
104
|
+
})
|
|
105
|
+
expect(response.data.length).toBeGreaterThan(0)
|
|
106
|
+
for (let log of response.data) {
|
|
107
|
+
expect(log.name.includes("User")).toBe(true)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
})
|