@budibase/worker 2.3.17 → 2.3.18-alpha.1
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 +13 -17
- package/package.json +11 -8
- 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/src/utilities/redis.ts +2 -0
- 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,48 @@
|
|
|
1
1
|
import { checkInviteCode } from "../../../utilities/redis"
|
|
2
|
-
import
|
|
2
|
+
import * as userSdk from "../../../sdk/users"
|
|
3
3
|
import env from "../../../environment"
|
|
4
4
|
import {
|
|
5
|
+
AcceptUserInviteRequest,
|
|
6
|
+
AcceptUserInviteResponse,
|
|
5
7
|
BulkUserRequest,
|
|
6
8
|
BulkUserResponse,
|
|
7
9
|
CloudAccount,
|
|
8
10
|
CreateAdminUserRequest,
|
|
11
|
+
CreateAdminUserResponse,
|
|
12
|
+
Ctx,
|
|
9
13
|
InviteUserRequest,
|
|
10
14
|
InviteUsersRequest,
|
|
15
|
+
MigrationType,
|
|
16
|
+
SaveUserResponse,
|
|
11
17
|
SearchUsersRequest,
|
|
12
18
|
User,
|
|
19
|
+
UserCtx,
|
|
13
20
|
} from "@budibase/types"
|
|
14
21
|
import {
|
|
15
22
|
accounts,
|
|
16
23
|
cache,
|
|
17
24
|
errors,
|
|
18
25
|
events,
|
|
26
|
+
migrations,
|
|
19
27
|
tenancy,
|
|
28
|
+
platform,
|
|
20
29
|
} from "@budibase/backend-core"
|
|
21
30
|
import { checkAnyUserExists } from "../../../utilities/users"
|
|
22
31
|
|
|
23
32
|
const MAX_USERS_UPLOAD_LIMIT = 1000
|
|
24
33
|
|
|
25
|
-
export const save = async (ctx:
|
|
34
|
+
export const save = async (ctx: UserCtx<User, SaveUserResponse>) => {
|
|
26
35
|
try {
|
|
27
36
|
const currentUserId = ctx.user._id
|
|
28
|
-
|
|
37
|
+
const requestUser = ctx.request.body
|
|
38
|
+
|
|
39
|
+
const user = await userSdk.save(requestUser, { currentUserId })
|
|
40
|
+
|
|
41
|
+
ctx.body = {
|
|
42
|
+
_id: user._id!,
|
|
43
|
+
_rev: user._rev!,
|
|
44
|
+
email: user.email,
|
|
45
|
+
}
|
|
29
46
|
} catch (err: any) {
|
|
30
47
|
ctx.throw(err.status || 400, err)
|
|
31
48
|
}
|
|
@@ -35,7 +52,7 @@ const bulkDelete = async (userIds: string[], currentUserId: string) => {
|
|
|
35
52
|
if (userIds?.indexOf(currentUserId) !== -1) {
|
|
36
53
|
throw new Error("Unable to delete self.")
|
|
37
54
|
}
|
|
38
|
-
return await
|
|
55
|
+
return await userSdk.bulkDelete(userIds)
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
const bulkCreate = async (users: User[], groupIds: string[]) => {
|
|
@@ -44,7 +61,7 @@ const bulkCreate = async (users: User[], groupIds: string[]) => {
|
|
|
44
61
|
"Max limit for upload is 1000 users. Please reduce file size and try again."
|
|
45
62
|
)
|
|
46
63
|
}
|
|
47
|
-
return await
|
|
64
|
+
return await userSdk.bulkCreate(users, groupIds)
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
export const bulkUpdate = async (ctx: any) => {
|
|
@@ -68,19 +85,30 @@ const parseBooleanParam = (param: any) => {
|
|
|
68
85
|
return !(param && param === "false")
|
|
69
86
|
}
|
|
70
87
|
|
|
71
|
-
export const adminUser = async (
|
|
72
|
-
|
|
73
|
-
|
|
88
|
+
export const adminUser = async (
|
|
89
|
+
ctx: Ctx<CreateAdminUserRequest, CreateAdminUserResponse>
|
|
90
|
+
) => {
|
|
91
|
+
const { email, password, tenantId } = ctx.request.body
|
|
92
|
+
|
|
93
|
+
if (await platform.tenants.exists(tenantId)) {
|
|
94
|
+
ctx.throw(403, "Organisation already exists.")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (env.MULTI_TENANCY) {
|
|
98
|
+
// store the new tenant record in the platform db
|
|
99
|
+
await platform.tenants.addTenant(tenantId)
|
|
100
|
+
await migrations.backPopulateMigrations({
|
|
101
|
+
type: MigrationType.GLOBAL,
|
|
102
|
+
tenantId,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
74
106
|
await tenancy.doInTenant(tenantId, async () => {
|
|
75
107
|
// account portal sends a pre-hashed password - honour param to prevent double hashing
|
|
76
108
|
const hashPassword = parseBooleanParam(ctx.request.query.hashPassword)
|
|
77
109
|
// account portal sends no password for SSO users
|
|
78
110
|
const requirePassword = parseBooleanParam(ctx.request.query.requirePassword)
|
|
79
111
|
|
|
80
|
-
if (await tenancy.doesTenantExist(tenantId)) {
|
|
81
|
-
ctx.throw(403, "Organisation already exists.")
|
|
82
|
-
}
|
|
83
|
-
|
|
84
112
|
const userExists = await checkAnyUserExists()
|
|
85
113
|
if (userExists) {
|
|
86
114
|
ctx.throw(
|
|
@@ -106,7 +134,7 @@ export const adminUser = async (ctx: any) => {
|
|
|
106
134
|
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
|
107
135
|
// stuck in a cycle
|
|
108
136
|
await cache.bustCache(cache.CacheKey.CHECKLIST)
|
|
109
|
-
const finalUser = await
|
|
137
|
+
const finalUser = await userSdk.save(user, {
|
|
110
138
|
hashPassword,
|
|
111
139
|
requirePassword,
|
|
112
140
|
})
|
|
@@ -118,7 +146,11 @@ export const adminUser = async (ctx: any) => {
|
|
|
118
146
|
}
|
|
119
147
|
await events.identification.identifyTenantGroup(tenantId, account)
|
|
120
148
|
|
|
121
|
-
ctx.body =
|
|
149
|
+
ctx.body = {
|
|
150
|
+
_id: finalUser._id!,
|
|
151
|
+
_rev: finalUser._rev!,
|
|
152
|
+
email: finalUser.email,
|
|
153
|
+
}
|
|
122
154
|
} catch (err: any) {
|
|
123
155
|
ctx.throw(err.status || 400, err)
|
|
124
156
|
}
|
|
@@ -128,7 +160,7 @@ export const adminUser = async (ctx: any) => {
|
|
|
128
160
|
export const countByApp = async (ctx: any) => {
|
|
129
161
|
const appId = ctx.params.appId
|
|
130
162
|
try {
|
|
131
|
-
ctx.body = await
|
|
163
|
+
ctx.body = await userSdk.countUsersByApp(appId)
|
|
132
164
|
} catch (err: any) {
|
|
133
165
|
ctx.throw(err.status || 400, err)
|
|
134
166
|
}
|
|
@@ -140,7 +172,7 @@ export const destroy = async (ctx: any) => {
|
|
|
140
172
|
ctx.throw(400, "Unable to delete self.")
|
|
141
173
|
}
|
|
142
174
|
|
|
143
|
-
await
|
|
175
|
+
await userSdk.destroy(id, ctx.user)
|
|
144
176
|
|
|
145
177
|
ctx.body = {
|
|
146
178
|
message: `User ${id} deleted.`,
|
|
@@ -149,7 +181,7 @@ export const destroy = async (ctx: any) => {
|
|
|
149
181
|
|
|
150
182
|
export const search = async (ctx: any) => {
|
|
151
183
|
const body = ctx.request.body as SearchUsersRequest
|
|
152
|
-
const paginated = await
|
|
184
|
+
const paginated = await userSdk.paginatedUsers(body)
|
|
153
185
|
// user hashed password shouldn't ever be returned
|
|
154
186
|
for (let user of paginated.data) {
|
|
155
187
|
if (user) {
|
|
@@ -161,7 +193,7 @@ export const search = async (ctx: any) => {
|
|
|
161
193
|
|
|
162
194
|
// called internally by app server user fetch
|
|
163
195
|
export const fetch = async (ctx: any) => {
|
|
164
|
-
const all = await
|
|
196
|
+
const all = await userSdk.allUsers()
|
|
165
197
|
// user hashed password shouldn't ever be returned
|
|
166
198
|
for (let user of all) {
|
|
167
199
|
if (user) {
|
|
@@ -173,12 +205,12 @@ export const fetch = async (ctx: any) => {
|
|
|
173
205
|
|
|
174
206
|
// called internally by app server user find
|
|
175
207
|
export const find = async (ctx: any) => {
|
|
176
|
-
ctx.body = await
|
|
208
|
+
ctx.body = await userSdk.getUser(ctx.params.id)
|
|
177
209
|
}
|
|
178
210
|
|
|
179
211
|
export const tenantUserLookup = async (ctx: any) => {
|
|
180
212
|
const id = ctx.params.id
|
|
181
|
-
const user = await
|
|
213
|
+
const user = await userSdk.getPlatformUser(id)
|
|
182
214
|
if (user) {
|
|
183
215
|
ctx.body = user
|
|
184
216
|
} else {
|
|
@@ -188,7 +220,7 @@ export const tenantUserLookup = async (ctx: any) => {
|
|
|
188
220
|
|
|
189
221
|
export const invite = async (ctx: any) => {
|
|
190
222
|
const request = ctx.request.body as InviteUserRequest
|
|
191
|
-
const response = await
|
|
223
|
+
const response = await userSdk.invite([request])
|
|
192
224
|
|
|
193
225
|
// explicitly throw for single user invite
|
|
194
226
|
if (response.unsuccessful.length) {
|
|
@@ -207,7 +239,7 @@ export const invite = async (ctx: any) => {
|
|
|
207
239
|
|
|
208
240
|
export const inviteMultiple = async (ctx: any) => {
|
|
209
241
|
const request = ctx.request.body as InviteUsersRequest
|
|
210
|
-
ctx.body = await
|
|
242
|
+
ctx.body = await userSdk.invite(request)
|
|
211
243
|
}
|
|
212
244
|
|
|
213
245
|
export const checkInvite = async (ctx: any) => {
|
|
@@ -223,13 +255,15 @@ export const checkInvite = async (ctx: any) => {
|
|
|
223
255
|
}
|
|
224
256
|
}
|
|
225
257
|
|
|
226
|
-
export const inviteAccept = async (
|
|
258
|
+
export const inviteAccept = async (
|
|
259
|
+
ctx: Ctx<AcceptUserInviteRequest, AcceptUserInviteResponse>
|
|
260
|
+
) => {
|
|
227
261
|
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
|
228
262
|
try {
|
|
229
263
|
// info is an extension of the user object that was stored by global
|
|
230
264
|
const { email, info }: any = await checkInviteCode(inviteCode)
|
|
231
|
-
|
|
232
|
-
const saved = await
|
|
265
|
+
const user = await tenancy.doInTenant(info.tenantId, async () => {
|
|
266
|
+
const saved = await userSdk.save({
|
|
233
267
|
firstName,
|
|
234
268
|
lastName,
|
|
235
269
|
password,
|
|
@@ -241,6 +275,12 @@ export const inviteAccept = async (ctx: any) => {
|
|
|
241
275
|
await events.user.inviteAccepted(user)
|
|
242
276
|
return saved
|
|
243
277
|
})
|
|
278
|
+
|
|
279
|
+
ctx.body = {
|
|
280
|
+
_id: user._id,
|
|
281
|
+
_rev: user._rev,
|
|
282
|
+
email: user.email,
|
|
283
|
+
}
|
|
244
284
|
} catch (err: any) {
|
|
245
285
|
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
|
246
286
|
// explicitly re-throw limit exceeded errors
|
|
@@ -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
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
+
import { CloudAccount, SSOUser, User } from "@budibase/types"
|
|
2
|
+
|
|
1
3
|
jest.mock("nodemailer")
|
|
2
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
TestConfiguration,
|
|
6
|
+
mocks,
|
|
7
|
+
structures,
|
|
8
|
+
generator,
|
|
9
|
+
} from "../../../../tests"
|
|
3
10
|
const sendMailMock = mocks.email.mock()
|
|
4
|
-
import { events } from "@budibase/backend-core"
|
|
11
|
+
import { events, constants } from "@budibase/backend-core"
|
|
12
|
+
import { Response } from "superagent"
|
|
13
|
+
|
|
14
|
+
import * as userSdk from "../../../../sdk/users"
|
|
15
|
+
|
|
16
|
+
function getAuthCookie(response: Response) {
|
|
17
|
+
return response.headers["set-cookie"]
|
|
18
|
+
.find((s: string) => s.startsWith(`${constants.Cookie.Auth}=`))
|
|
19
|
+
.split("=")[1]
|
|
20
|
+
.split(";")[0]
|
|
21
|
+
}
|
|
5
22
|
|
|
6
|
-
const expectSetAuthCookie = (
|
|
7
|
-
expect(
|
|
8
|
-
res.get("Set-Cookie").find((c: string) => c.startsWith("budibase:auth"))
|
|
9
|
-
).toBeDefined()
|
|
23
|
+
const expectSetAuthCookie = (response: Response) => {
|
|
24
|
+
expect(getAuthCookie(response).length > 1).toBe(true)
|
|
10
25
|
}
|
|
11
26
|
|
|
12
27
|
describe("/api/global/auth", () => {
|
|
@@ -24,53 +39,247 @@ describe("/api/global/auth", () => {
|
|
|
24
39
|
jest.clearAllMocks()
|
|
25
40
|
})
|
|
26
41
|
|
|
42
|
+
async function createSSOUser() {
|
|
43
|
+
return config.doInTenant(async () => {
|
|
44
|
+
return userSdk.save(structures.users.ssoUser(), {
|
|
45
|
+
requirePassword: false,
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
27
50
|
describe("password", () => {
|
|
28
51
|
describe("POST /api/global/auth/:tenantId/login", () => {
|
|
29
|
-
it("
|
|
52
|
+
it("logs in with correct credentials", async () => {
|
|
53
|
+
const tenantId = config.tenantId!
|
|
54
|
+
const email = config.user?.email!
|
|
55
|
+
const password = config.userPassword
|
|
56
|
+
|
|
57
|
+
const response = await config.api.auth.login(tenantId, email, password)
|
|
58
|
+
|
|
59
|
+
expectSetAuthCookie(response)
|
|
60
|
+
expect(events.auth.login).toBeCalledTimes(1)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("should return 403 with incorrect credentials", async () => {
|
|
64
|
+
const tenantId = config.tenantId!
|
|
65
|
+
const email = config.user?.email!
|
|
66
|
+
const password = "incorrect"
|
|
67
|
+
|
|
68
|
+
const response = await config.api.auth.login(
|
|
69
|
+
tenantId,
|
|
70
|
+
email,
|
|
71
|
+
password,
|
|
72
|
+
{ status: 403 }
|
|
73
|
+
)
|
|
74
|
+
expect(response.body).toEqual({
|
|
75
|
+
message: "Invalid credentials",
|
|
76
|
+
status: 403,
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("should return 403 when user doesn't exist", async () => {
|
|
81
|
+
const tenantId = config.tenantId!
|
|
82
|
+
const email = "invaliduser@test.com"
|
|
83
|
+
const password = "password"
|
|
84
|
+
|
|
85
|
+
const response = await config.api.auth.login(
|
|
86
|
+
tenantId,
|
|
87
|
+
email,
|
|
88
|
+
password,
|
|
89
|
+
{ status: 403 }
|
|
90
|
+
)
|
|
91
|
+
expect(response.body).toEqual({
|
|
92
|
+
message: "Invalid credentials",
|
|
93
|
+
status: 403,
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe("sso user", () => {
|
|
98
|
+
let user: User
|
|
99
|
+
|
|
100
|
+
async function testSSOUser() {
|
|
101
|
+
const tenantId = user.tenantId!
|
|
102
|
+
const email = user.email
|
|
103
|
+
const password = "test"
|
|
104
|
+
|
|
105
|
+
const response = await config.api.auth.login(
|
|
106
|
+
tenantId,
|
|
107
|
+
email,
|
|
108
|
+
password,
|
|
109
|
+
{ status: 400 }
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
expect(response.body).toEqual({
|
|
113
|
+
message: "SSO user cannot login using password",
|
|
114
|
+
status: 400,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
describe("budibase sso user", () => {
|
|
119
|
+
it("should prevent user from logging in", async () => {
|
|
120
|
+
user = await createSSOUser()
|
|
121
|
+
await testSSOUser()
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe("root account sso user", () => {
|
|
126
|
+
it("should prevent user from logging in", async () => {
|
|
127
|
+
user = await config.createUser()
|
|
128
|
+
const account = structures.accounts.ssoAccount() as CloudAccount
|
|
129
|
+
mocks.accounts.getAccount.mockReturnValueOnce(
|
|
130
|
+
Promise.resolve(account)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
await testSSOUser()
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
})
|
|
30
137
|
})
|
|
31
138
|
|
|
32
139
|
describe("POST /api/global/auth/logout", () => {
|
|
33
140
|
it("should logout", async () => {
|
|
34
|
-
await config.api.auth.logout()
|
|
141
|
+
const response = await config.api.auth.logout()
|
|
35
142
|
expect(events.auth.logout).toBeCalledTimes(1)
|
|
36
143
|
|
|
37
|
-
|
|
144
|
+
const authCookie = getAuthCookie(response)
|
|
145
|
+
expect(authCookie).toBe("")
|
|
38
146
|
})
|
|
39
147
|
})
|
|
40
148
|
|
|
41
149
|
describe("POST /api/global/auth/:tenantId/reset", () => {
|
|
42
150
|
it("should generate password reset email", async () => {
|
|
151
|
+
const user = await config.createUser()
|
|
152
|
+
|
|
43
153
|
const { res, code } = await config.api.auth.requestPasswordReset(
|
|
44
|
-
sendMailMock
|
|
154
|
+
sendMailMock,
|
|
155
|
+
user.email
|
|
45
156
|
)
|
|
46
|
-
const user = await config.getUser("test@test.com")
|
|
47
157
|
|
|
48
158
|
expect(res.body).toEqual({
|
|
49
159
|
message: "Please check your email for a reset link.",
|
|
50
160
|
})
|
|
51
161
|
expect(sendMailMock).toHaveBeenCalled()
|
|
52
|
-
|
|
53
162
|
expect(code).toBeDefined()
|
|
54
163
|
expect(events.user.passwordResetRequested).toBeCalledTimes(1)
|
|
55
164
|
expect(events.user.passwordResetRequested).toBeCalledWith(user)
|
|
56
165
|
})
|
|
166
|
+
|
|
167
|
+
describe("sso user", () => {
|
|
168
|
+
let user: User
|
|
169
|
+
|
|
170
|
+
async function testSSOUser() {
|
|
171
|
+
const { res } = await config.api.auth.requestPasswordReset(
|
|
172
|
+
sendMailMock,
|
|
173
|
+
user.email,
|
|
174
|
+
{ status: 400 }
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
expect(res.body).toEqual({
|
|
178
|
+
message: "SSO user cannot reset password",
|
|
179
|
+
status: 400,
|
|
180
|
+
error: {
|
|
181
|
+
code: "http",
|
|
182
|
+
type: "generic",
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
expect(sendMailMock).not.toHaveBeenCalled()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
describe("budibase sso user", () => {
|
|
189
|
+
it("should prevent user from generating password reset email", async () => {
|
|
190
|
+
user = await createSSOUser()
|
|
191
|
+
await testSSOUser()
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe("root account sso user", () => {
|
|
196
|
+
it("should prevent user from generating password reset email", async () => {
|
|
197
|
+
user = await config.createUser(structures.users.user())
|
|
198
|
+
const account = structures.accounts.ssoAccount() as CloudAccount
|
|
199
|
+
mocks.accounts.getAccount.mockReturnValueOnce(
|
|
200
|
+
Promise.resolve(account)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
await testSSOUser()
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
})
|
|
57
207
|
})
|
|
58
208
|
|
|
59
209
|
describe("POST /api/global/auth/:tenantId/reset/update", () => {
|
|
60
210
|
it("should reset password", async () => {
|
|
211
|
+
let user = await config.createUser()
|
|
61
212
|
const { code } = await config.api.auth.requestPasswordReset(
|
|
62
|
-
sendMailMock
|
|
213
|
+
sendMailMock,
|
|
214
|
+
user.email
|
|
63
215
|
)
|
|
64
|
-
const user = await config.getUser("test@test.com")
|
|
65
216
|
delete user.password
|
|
66
217
|
|
|
67
|
-
const
|
|
218
|
+
const newPassword = "newpassword"
|
|
219
|
+
const res = await config.api.auth.updatePassword(code!, newPassword)
|
|
220
|
+
|
|
221
|
+
user = await config.getUser(user.email)
|
|
222
|
+
delete user.password
|
|
68
223
|
|
|
69
224
|
expect(res.body).toEqual({ message: "password reset successfully." })
|
|
70
225
|
expect(events.user.passwordReset).toBeCalledTimes(1)
|
|
71
226
|
expect(events.user.passwordReset).toBeCalledWith(user)
|
|
72
227
|
|
|
73
|
-
//
|
|
228
|
+
// login using new password
|
|
229
|
+
await config.api.auth.login(user.tenantId, user.email, newPassword)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe("sso user", () => {
|
|
233
|
+
let user: User | SSOUser
|
|
234
|
+
|
|
235
|
+
async function testSSOUser(code: string) {
|
|
236
|
+
const res = await config.api.auth.updatePassword(
|
|
237
|
+
code!,
|
|
238
|
+
generator.string(),
|
|
239
|
+
{ status: 400 }
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
expect(res.body).toEqual({
|
|
243
|
+
message: "Cannot reset password.",
|
|
244
|
+
status: 400,
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
describe("budibase sso user", () => {
|
|
249
|
+
it("should prevent user from generating password reset email", async () => {
|
|
250
|
+
user = await config.createUser()
|
|
251
|
+
const { code } = await config.api.auth.requestPasswordReset(
|
|
252
|
+
sendMailMock,
|
|
253
|
+
user.email
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
// convert to sso now that password reset has been requested
|
|
257
|
+
const ssoUser = user as SSOUser
|
|
258
|
+
ssoUser.providerType = structures.sso.providerType()
|
|
259
|
+
delete ssoUser.password
|
|
260
|
+
await config.doInTenant(() => userSdk.save(ssoUser))
|
|
261
|
+
|
|
262
|
+
await testSSOUser(code!)
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
describe("root account sso user", () => {
|
|
267
|
+
it("should prevent user from generating password reset email", async () => {
|
|
268
|
+
user = await config.createUser()
|
|
269
|
+
const { code } = await config.api.auth.requestPasswordReset(
|
|
270
|
+
sendMailMock,
|
|
271
|
+
user.email
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
// convert to account owner now that password has been requested
|
|
275
|
+
const account = structures.accounts.ssoAccount() as CloudAccount
|
|
276
|
+
mocks.accounts.getAccount.mockReturnValueOnce(
|
|
277
|
+
Promise.resolve(account)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
await testSSOUser(code!)
|
|
281
|
+
})
|
|
282
|
+
})
|
|
74
283
|
})
|
|
75
284
|
})
|
|
76
285
|
})
|
|
@@ -145,7 +354,7 @@ describe("/api/global/auth", () => {
|
|
|
145
354
|
const location: string = res.get("location")
|
|
146
355
|
expect(
|
|
147
356
|
location.startsWith(
|
|
148
|
-
|
|
357
|
+
`http://localhost/auth?response_type=code&client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A10000%2Fapi%2Fglobal%2Fauth%2F${config.tenantId}%2Foidc%2Fcallback&scope=openid%20profile%20email%20offline_access`
|
|
149
358
|
)
|
|
150
359
|
).toBe(true)
|
|
151
360
|
})
|