@budibase/worker 2.3.17-alpha.4 → 2.3.17-alpha.5
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/package.json +8 -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 +234 -33
- package/src/api/routes/global/tests/configs.spec.ts +93 -113
- package/src/api/routes/global/tests/roles.spec.ts +3 -2
- package/src/api/routes/global/tests/self.spec.ts +3 -4
- package/src/api/routes/global/tests/users.spec.ts +44 -46
- 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/environment.ts +15 -1
- package/src/index.ts +6 -0
- 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 +38 -89
- package/src/tests/api/auth.ts +42 -18
- package/src/tests/api/base.ts +2 -1
- package/src/tests/api/restore.ts +1 -0
- package/src/tests/jestEnv.ts +2 -1
- package/src/tests/jestSetup.ts +2 -5
- package/src/tests/logging.ts +34 -0
- package/src/tests/structures/index.ts +0 -2
- package/src/utilities/email.ts +3 -3
- package/src/tests/structures/users.ts +0 -37
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/worker",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "2.3.17-alpha.
|
|
4
|
+
"version": "2.3.17-alpha.5",
|
|
5
5
|
"description": "Budibase background service",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"author": "Budibase",
|
|
37
37
|
"license": "GPL-3.0",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@budibase/backend-core": "2.3.17-alpha.
|
|
40
|
-
"@budibase/pro": "2.3.17-alpha.
|
|
41
|
-
"@budibase/string-templates": "2.3.17-alpha.
|
|
42
|
-
"@budibase/types": "2.3.17-alpha.
|
|
39
|
+
"@budibase/backend-core": "2.3.17-alpha.5",
|
|
40
|
+
"@budibase/pro": "2.3.17-alpha.4",
|
|
41
|
+
"@budibase/string-templates": "2.3.17-alpha.5",
|
|
42
|
+
"@budibase/types": "2.3.17-alpha.5",
|
|
43
43
|
"@koa/router": "8.0.8",
|
|
44
44
|
"@sentry/node": "6.17.7",
|
|
45
45
|
"@techpass/passport-openidconnect": "0.3.2",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@swc/core": "^1.3.25",
|
|
75
75
|
"@swc/jest": "^0.2.24",
|
|
76
76
|
"@trendyol/jest-testcontainers": "^2.1.1",
|
|
77
|
-
"@types/jest": "
|
|
77
|
+
"@types/jest": "28.1.1",
|
|
78
78
|
"@types/jsonwebtoken": "8.5.1",
|
|
79
79
|
"@types/koa": "2.13.4",
|
|
80
80
|
"@types/koa__router": "8.0.8",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"@types/node-fetch": "2.6.1",
|
|
83
83
|
"@types/pouchdb": "6.4.0",
|
|
84
84
|
"@types/server-destroy": "1.0.1",
|
|
85
|
+
"@types/supertest": "2.0.12",
|
|
85
86
|
"@types/uuid": "8.3.4",
|
|
86
87
|
"@typescript-eslint/parser": "5.45.0",
|
|
87
88
|
"copyfiles": "2.4.1",
|
|
@@ -99,5 +100,5 @@
|
|
|
99
100
|
"typescript": "4.7.3",
|
|
100
101
|
"update-dotenv": "1.1.1"
|
|
101
102
|
},
|
|
102
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "f336cec967d590cae2b6bb5ff78b9ccc84c418f1"
|
|
103
104
|
}
|
package/scripts/dev/manage.js
CHANGED
|
@@ -29,6 +29,7 @@ async function init() {
|
|
|
29
29
|
SERVICE: "worker-service",
|
|
30
30
|
DEPLOYMENT_ENVIRONMENT: "development",
|
|
31
31
|
TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR",
|
|
32
|
+
ENABLE_EMAIL_TEST_MODE: 1,
|
|
32
33
|
}
|
|
33
34
|
let envFile = ""
|
|
34
35
|
Object.keys(envFileJson).forEach(key => {
|
|
@@ -1,49 +1,55 @@
|
|
|
1
1
|
import {
|
|
2
|
-
auth,
|
|
2
|
+
auth as authCore,
|
|
3
3
|
constants,
|
|
4
4
|
context,
|
|
5
5
|
db as dbCore,
|
|
6
6
|
events,
|
|
7
7
|
tenancy,
|
|
8
|
-
|
|
9
|
-
utils,
|
|
8
|
+
utils as utilsCore,
|
|
10
9
|
} from "@budibase/backend-core"
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
import {
|
|
11
|
+
ConfigType,
|
|
12
|
+
User,
|
|
13
|
+
Ctx,
|
|
14
|
+
LoginRequest,
|
|
15
|
+
SSOUser,
|
|
16
|
+
PasswordResetRequest,
|
|
17
|
+
PasswordResetUpdateRequest,
|
|
18
|
+
} from "@budibase/types"
|
|
14
19
|
import env from "../../../environment"
|
|
15
|
-
import sdk from "../../../sdk"
|
|
16
|
-
import { ConfigType, User } from "@budibase/types"
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const { passport, ssoCallbackUrl, google, oidc } = auth
|
|
21
|
+
import * as authSdk from "../../../sdk/auth"
|
|
22
|
+
import * as userSdk from "../../../sdk/users"
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
24
|
+
const { Cookie, Header } = constants
|
|
25
|
+
const { passport, ssoCallbackUrl, google, oidc } = authCore
|
|
26
|
+
const { setCookie, getCookie, clearCookie } = utilsCore
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
return ssoCallbackUrl(tenancy.getGlobalDB(), config, ConfigType.OIDC)
|
|
28
|
-
}
|
|
28
|
+
// LOGIN / LOGOUT
|
|
29
29
|
|
|
30
|
-
async function
|
|
30
|
+
async function passportCallback(
|
|
31
|
+
ctx: Ctx,
|
|
32
|
+
user: User,
|
|
33
|
+
err: any = null,
|
|
34
|
+
info: { message: string } | null = null
|
|
35
|
+
) {
|
|
31
36
|
if (err) {
|
|
32
37
|
console.error("Authentication error")
|
|
33
38
|
console.error(err)
|
|
34
39
|
console.trace(err)
|
|
35
40
|
return ctx.throw(403, info ? info : "Unauthorized")
|
|
36
41
|
}
|
|
37
|
-
|
|
38
42
|
if (!user) {
|
|
39
43
|
console.error("Authentication error - no user provided")
|
|
40
44
|
return ctx.throw(403, info ? info : "Unauthorized")
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
const token = await authSdk.loginUser(user)
|
|
48
|
+
|
|
43
49
|
// set a cookie for browser access
|
|
44
|
-
setCookie(ctx,
|
|
50
|
+
setCookie(ctx, token, Cookie.Auth, { sign: false })
|
|
45
51
|
// set the token in a header as well for APIs
|
|
46
|
-
ctx.set(Header.TOKEN,
|
|
52
|
+
ctx.set(Header.TOKEN, token)
|
|
47
53
|
// get rid of any app cookies on login
|
|
48
54
|
// have to check test because this breaks cypress
|
|
49
55
|
if (!env.isTest()) {
|
|
@@ -51,11 +57,18 @@ async function authInternal(ctx: any, user: any, err: any = null, info = null) {
|
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
export const
|
|
60
|
+
export const login = async (ctx: Ctx<LoginRequest>, next: any) => {
|
|
61
|
+
const email = ctx.request.body.username
|
|
62
|
+
|
|
63
|
+
const user = await userSdk.getUserByEmail(email)
|
|
64
|
+
if (user && (await userSdk.isPreventSSOPasswords(user))) {
|
|
65
|
+
ctx.throw(400, "SSO user cannot login using password")
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
return passport.authenticate(
|
|
56
69
|
"local",
|
|
57
70
|
async (err: any, user: User, info: any) => {
|
|
58
|
-
await
|
|
71
|
+
await passportCallback(ctx, user, err, info)
|
|
59
72
|
await context.identity.doInUserContext(user, async () => {
|
|
60
73
|
await events.auth.login("local")
|
|
61
74
|
})
|
|
@@ -64,6 +77,15 @@ export const authenticate = async (ctx: any, next: any) => {
|
|
|
64
77
|
)(ctx, next)
|
|
65
78
|
}
|
|
66
79
|
|
|
80
|
+
export const logout = async (ctx: any) => {
|
|
81
|
+
if (ctx.user && ctx.user._id) {
|
|
82
|
+
await authSdk.logout({ ctx, userId: ctx.user._id })
|
|
83
|
+
}
|
|
84
|
+
ctx.body = { message: "User logged out." }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// INIT
|
|
88
|
+
|
|
67
89
|
export const setInitInfo = (ctx: any) => {
|
|
68
90
|
const initInfo = ctx.request.body
|
|
69
91
|
setCookie(ctx, initInfo, Cookie.Init)
|
|
@@ -79,32 +101,16 @@ export const getInitInfo = (ctx: any) => {
|
|
|
79
101
|
}
|
|
80
102
|
}
|
|
81
103
|
|
|
104
|
+
// PASSWORD MANAGEMENT
|
|
105
|
+
|
|
82
106
|
/**
|
|
83
107
|
* Reset the user password, used as part of a forgotten password flow.
|
|
84
108
|
*/
|
|
85
|
-
export const reset = async (ctx:
|
|
109
|
+
export const reset = async (ctx: Ctx<PasswordResetRequest>) => {
|
|
86
110
|
const { email } = ctx.request.body
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
400,
|
|
91
|
-
"Please contact your platform administrator, SMTP is not configured."
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
try {
|
|
95
|
-
const user = (await usersCore.getGlobalUserByEmail(email)) as User
|
|
96
|
-
// only if user exists, don't error though if they don't
|
|
97
|
-
if (user) {
|
|
98
|
-
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
|
|
99
|
-
user,
|
|
100
|
-
subject: "{{ company }} platform password reset",
|
|
101
|
-
})
|
|
102
|
-
await events.user.passwordResetRequested(user)
|
|
103
|
-
}
|
|
104
|
-
} catch (err) {
|
|
105
|
-
console.log(err)
|
|
106
|
-
// don't throw any kind of error to the user, this might give away something
|
|
107
|
-
}
|
|
111
|
+
|
|
112
|
+
await authSdk.reset(email)
|
|
113
|
+
|
|
108
114
|
ctx.body = {
|
|
109
115
|
message: "Please check your email for a reset link.",
|
|
110
116
|
}
|
|
@@ -113,32 +119,21 @@ export const reset = async (ctx: any) => {
|
|
|
113
119
|
/**
|
|
114
120
|
* Perform the user password update if the provided reset code is valid.
|
|
115
121
|
*/
|
|
116
|
-
export const resetUpdate = async (ctx:
|
|
122
|
+
export const resetUpdate = async (ctx: Ctx<PasswordResetUpdateRequest>) => {
|
|
117
123
|
const { resetCode, password } = ctx.request.body
|
|
118
124
|
try {
|
|
119
|
-
|
|
120
|
-
const db = tenancy.getGlobalDB()
|
|
121
|
-
const user = await db.get(userId)
|
|
122
|
-
user.password = await hash(password)
|
|
123
|
-
await db.put(user)
|
|
125
|
+
await authSdk.resetUpdate(resetCode, password)
|
|
124
126
|
ctx.body = {
|
|
125
127
|
message: "password reset successfully.",
|
|
126
128
|
}
|
|
127
|
-
// remove password from the user before sending events
|
|
128
|
-
delete user.password
|
|
129
|
-
await events.user.passwordReset(user)
|
|
130
129
|
} catch (err) {
|
|
131
|
-
console.
|
|
130
|
+
console.warn(err)
|
|
131
|
+
// hide any details of the error for security
|
|
132
132
|
ctx.throw(400, "Cannot reset password.")
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
if (ctx.user && ctx.user._id) {
|
|
138
|
-
await platformLogout({ ctx, userId: ctx.user._id })
|
|
139
|
-
}
|
|
140
|
-
ctx.body = { message: "User logged out." }
|
|
141
|
-
}
|
|
136
|
+
// DATASOURCE
|
|
142
137
|
|
|
143
138
|
export const datasourcePreAuth = async (ctx: any, next: any) => {
|
|
144
139
|
const provider = ctx.params.provider
|
|
@@ -166,6 +161,12 @@ export const datasourceAuth = async (ctx: any, next: any) => {
|
|
|
166
161
|
return handler.postAuth(passport, ctx, next)
|
|
167
162
|
}
|
|
168
163
|
|
|
164
|
+
// GOOGLE SSO
|
|
165
|
+
|
|
166
|
+
export async function googleCallbackUrl(config?: { callbackURL?: string }) {
|
|
167
|
+
return ssoCallbackUrl(tenancy.getGlobalDB(), config, ConfigType.GOOGLE)
|
|
168
|
+
}
|
|
169
|
+
|
|
169
170
|
/**
|
|
170
171
|
* The initial call that google authentication makes to take you to the google login screen.
|
|
171
172
|
* On a successful login, you will be redirected to the googleAuth callback route.
|
|
@@ -181,7 +182,7 @@ export const googlePreAuth = async (ctx: any, next: any) => {
|
|
|
181
182
|
const strategy = await google.strategyFactory(
|
|
182
183
|
config,
|
|
183
184
|
callbackUrl,
|
|
184
|
-
|
|
185
|
+
userSdk.save
|
|
185
186
|
)
|
|
186
187
|
|
|
187
188
|
return passport.authenticate(strategy, {
|
|
@@ -191,7 +192,7 @@ export const googlePreAuth = async (ctx: any, next: any) => {
|
|
|
191
192
|
})(ctx, next)
|
|
192
193
|
}
|
|
193
194
|
|
|
194
|
-
export const
|
|
195
|
+
export const googleCallback = async (ctx: any, next: any) => {
|
|
195
196
|
const db = tenancy.getGlobalDB()
|
|
196
197
|
|
|
197
198
|
const config = await dbCore.getScopedConfig(db, {
|
|
@@ -202,14 +203,14 @@ export const googleAuth = async (ctx: any, next: any) => {
|
|
|
202
203
|
const strategy = await google.strategyFactory(
|
|
203
204
|
config,
|
|
204
205
|
callbackUrl,
|
|
205
|
-
|
|
206
|
+
userSdk.save
|
|
206
207
|
)
|
|
207
208
|
|
|
208
209
|
return passport.authenticate(
|
|
209
210
|
strategy,
|
|
210
211
|
{ successRedirect: "/", failureRedirect: "/error" },
|
|
211
|
-
async (err: any, user:
|
|
212
|
-
await
|
|
212
|
+
async (err: any, user: SSOUser, info: any) => {
|
|
213
|
+
await passportCallback(ctx, user, err, info)
|
|
213
214
|
await context.identity.doInUserContext(user, async () => {
|
|
214
215
|
await events.auth.login("google-internal")
|
|
215
216
|
})
|
|
@@ -218,6 +219,12 @@ export const googleAuth = async (ctx: any, next: any) => {
|
|
|
218
219
|
)(ctx, next)
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
// OIDC SSO
|
|
223
|
+
|
|
224
|
+
export async function oidcCallbackUrl(config?: { callbackURL?: string }) {
|
|
225
|
+
return ssoCallbackUrl(tenancy.getGlobalDB(), config, ConfigType.OIDC)
|
|
226
|
+
}
|
|
227
|
+
|
|
221
228
|
export const oidcStrategyFactory = async (ctx: any, configId: any) => {
|
|
222
229
|
const db = tenancy.getGlobalDB()
|
|
223
230
|
const config = await dbCore.getScopedConfig(db, {
|
|
@@ -233,7 +240,7 @@ export const oidcStrategyFactory = async (ctx: any, configId: any) => {
|
|
|
233
240
|
chosenConfig,
|
|
234
241
|
callbackUrl
|
|
235
242
|
)
|
|
236
|
-
return oidc.strategyFactory(enrichedConfig,
|
|
243
|
+
return oidc.strategyFactory(enrichedConfig, userSdk.save)
|
|
237
244
|
}
|
|
238
245
|
|
|
239
246
|
/**
|
|
@@ -265,15 +272,15 @@ export const oidcPreAuth = async (ctx: any, next: any) => {
|
|
|
265
272
|
})(ctx, next)
|
|
266
273
|
}
|
|
267
274
|
|
|
268
|
-
export const
|
|
275
|
+
export const oidcCallback = async (ctx: any, next: any) => {
|
|
269
276
|
const configId = getCookie(ctx, Cookie.OIDC_CONFIG)
|
|
270
277
|
const strategy = await oidcStrategyFactory(ctx, configId)
|
|
271
278
|
|
|
272
279
|
return passport.authenticate(
|
|
273
280
|
strategy,
|
|
274
281
|
{ successRedirect: "/", failureRedirect: "/error" },
|
|
275
|
-
async (err: any, user:
|
|
276
|
-
await
|
|
282
|
+
async (err: any, user: SSOUser, info: any) => {
|
|
283
|
+
await passportCallback(ctx, user, err, info)
|
|
277
284
|
await context.identity.doInUserContext(user, async () => {
|
|
278
285
|
await events.auth.login("oidc")
|
|
279
286
|
})
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as userSdk from "../../../sdk/users"
|
|
2
2
|
import {
|
|
3
|
-
events,
|
|
4
3
|
featureFlags,
|
|
5
4
|
tenancy,
|
|
6
5
|
constants,
|
|
7
6
|
db as dbCore,
|
|
8
7
|
utils,
|
|
9
|
-
cache,
|
|
10
8
|
encryption,
|
|
9
|
+
auth as authCore,
|
|
11
10
|
} from "@budibase/backend-core"
|
|
12
11
|
import env from "../../../environment"
|
|
13
12
|
import { groups } from "@budibase/pro"
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
import {
|
|
14
|
+
UpdateSelfRequest,
|
|
15
|
+
UpdateSelfResponse,
|
|
16
|
+
UpdateSelf,
|
|
17
|
+
UserCtx,
|
|
18
|
+
} from "@budibase/types"
|
|
19
|
+
const { getCookie, clearCookie, newid } = utils
|
|
16
20
|
|
|
17
21
|
function newTestApiKey() {
|
|
18
22
|
return env.ENCRYPTED_TEST_PUBLIC_API_KEY
|
|
@@ -93,17 +97,6 @@ const addSessionAttributesToUser = (ctx: any) => {
|
|
|
93
97
|
ctx.body.csrfToken = ctx.user.csrfToken
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
const sanitiseUserUpdate = (ctx: any) => {
|
|
97
|
-
const allowed = ["firstName", "lastName", "password", "forceResetPassword"]
|
|
98
|
-
const resp: { [key: string]: any } = {}
|
|
99
|
-
for (let [key, value] of Object.entries(ctx.request.body)) {
|
|
100
|
-
if (allowed.includes(key)) {
|
|
101
|
-
resp[key] = value
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return resp
|
|
105
|
-
}
|
|
106
|
-
|
|
107
100
|
export async function getSelf(ctx: any) {
|
|
108
101
|
if (!ctx.user) {
|
|
109
102
|
ctx.throw(403, "User not logged in")
|
|
@@ -116,7 +109,7 @@ export async function getSelf(ctx: any) {
|
|
|
116
109
|
checkCurrentApp(ctx)
|
|
117
110
|
|
|
118
111
|
// get the main body of the user
|
|
119
|
-
const user = await
|
|
112
|
+
const user = await userSdk.getUser(userId)
|
|
120
113
|
ctx.body = await groups.enrichUserRolesFromGroups(user)
|
|
121
114
|
|
|
122
115
|
// add the feature flags for this tenant
|
|
@@ -126,39 +119,30 @@ export async function getSelf(ctx: any) {
|
|
|
126
119
|
addSessionAttributesToUser(ctx)
|
|
127
120
|
}
|
|
128
121
|
|
|
129
|
-
export async function updateSelf(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
122
|
+
export async function updateSelf(
|
|
123
|
+
ctx: UserCtx<UpdateSelfRequest, UpdateSelfResponse>
|
|
124
|
+
) {
|
|
125
|
+
const body = ctx.request.body
|
|
126
|
+
const update: UpdateSelf = {
|
|
127
|
+
firstName: body.firstName,
|
|
128
|
+
lastName: body.lastName,
|
|
129
|
+
password: body.password,
|
|
130
|
+
forceResetPassword: body.forceResetPassword,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const user = await userSdk.updateSelf(ctx.user._id!, update)
|
|
134
|
+
|
|
135
|
+
if (update.password) {
|
|
139
136
|
// Log all other sessions out apart from the current one
|
|
140
|
-
await platformLogout({
|
|
137
|
+
await authCore.platformLogout({
|
|
141
138
|
ctx,
|
|
142
|
-
userId: ctx.user._id
|
|
139
|
+
userId: ctx.user._id!,
|
|
143
140
|
keepActiveSession: true,
|
|
144
141
|
})
|
|
145
142
|
}
|
|
146
143
|
|
|
147
|
-
const response = await db.put({
|
|
148
|
-
...user,
|
|
149
|
-
...userUpdateObj,
|
|
150
|
-
})
|
|
151
|
-
await userCache.invalidateUser(user._id)
|
|
152
144
|
ctx.body = {
|
|
153
|
-
_id:
|
|
154
|
-
_rev:
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// remove the old password from the user before sending events
|
|
158
|
-
user._rev = response.rev
|
|
159
|
-
delete user.password
|
|
160
|
-
await events.user.updated(user)
|
|
161
|
-
if (passwordChange) {
|
|
162
|
-
await events.user.passwordUpdated(user)
|
|
145
|
+
_id: user._id!,
|
|
146
|
+
_rev: user._rev!,
|
|
163
147
|
}
|
|
164
148
|
}
|
|
@@ -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
|