@budibase/worker 1.0.123 → 1.0.125
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/nodemon.json +1 -1
- package/package.json +11 -6
- package/src/api/controllers/global/{auth.js → auth.ts} +31 -27
- package/src/api/controllers/global/license.ts +30 -0
- package/src/api/controllers/global/self.js +32 -7
- package/src/api/controllers/global/{users.js → users.ts} +39 -35
- package/src/api/index.js +6 -1
- package/src/api/routes/global/license.ts +12 -0
- package/src/api/routes/index.js +2 -0
- package/src/api/routes/tests/auth.spec.js +2 -1
- package/src/environment.js +1 -1
- package/src/index.ts +4 -0
package/nodemon.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/worker",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.125",
|
|
5
5
|
"description": "Budibase background service",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -31,14 +31,17 @@
|
|
|
31
31
|
"author": "Budibase",
|
|
32
32
|
"license": "GPL-3.0",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@budibase/backend-core": "^1.0.
|
|
35
|
-
"@budibase/
|
|
34
|
+
"@budibase/backend-core": "^1.0.125",
|
|
35
|
+
"@budibase/pro": "1.0.124-alpha.0",
|
|
36
|
+
"@budibase/string-templates": "^1.0.125",
|
|
36
37
|
"@koa/router": "^8.0.0",
|
|
37
|
-
"@sentry/node": "
|
|
38
|
+
"@sentry/node": "6.17.7",
|
|
38
39
|
"@techpass/passport-openidconnect": "^0.3.0",
|
|
40
|
+
"@types/global-agent": "^2.1.1",
|
|
39
41
|
"aws-sdk": "^2.811.0",
|
|
40
42
|
"bcryptjs": "^2.4.3",
|
|
41
43
|
"dotenv": "^8.2.0",
|
|
44
|
+
"global-agent": "^3.0.0",
|
|
42
45
|
"got": "^11.8.1",
|
|
43
46
|
"joi": "^17.4.0",
|
|
44
47
|
"koa": "^2.7.0",
|
|
@@ -63,7 +66,9 @@
|
|
|
63
66
|
"@types/jest": "^26.0.23",
|
|
64
67
|
"@types/koa": "^2.13.3",
|
|
65
68
|
"@types/koa-router": "^7.4.2",
|
|
69
|
+
"@types/koa__router": "^8.0.11",
|
|
66
70
|
"@types/node": "^15.12.4",
|
|
71
|
+
"@typescript-eslint/parser": "5.12.0",
|
|
67
72
|
"copyfiles": "^2.4.1",
|
|
68
73
|
"eslint": "^6.8.0",
|
|
69
74
|
"jest": "^27.0.5",
|
|
@@ -74,7 +79,7 @@
|
|
|
74
79
|
"supertest": "^6.1.3",
|
|
75
80
|
"ts-jest": "^27.0.3",
|
|
76
81
|
"ts-node": "^10.0.0",
|
|
77
|
-
"typescript": "4.
|
|
82
|
+
"typescript": "4.5.5",
|
|
78
83
|
"update-dotenv": "^1.1.1"
|
|
79
84
|
},
|
|
80
85
|
"jest": {
|
|
@@ -84,5 +89,5 @@
|
|
|
84
89
|
"./scripts/jestSetup.js"
|
|
85
90
|
]
|
|
86
91
|
},
|
|
87
|
-
"gitHead": "
|
|
92
|
+
"gitHead": "e96d44823de430b3f487705d18d34954f8fb9bf7"
|
|
88
93
|
}
|
|
@@ -21,8 +21,9 @@ const {
|
|
|
21
21
|
isMultiTenant,
|
|
22
22
|
} = require("@budibase/backend-core/tenancy")
|
|
23
23
|
const env = require("../../../environment")
|
|
24
|
+
import { users } from "@budibase/pro"
|
|
24
25
|
|
|
25
|
-
const ssoCallbackUrl = async (config, type) => {
|
|
26
|
+
const ssoCallbackUrl = async (config: any, type: any) => {
|
|
26
27
|
// incase there is a callback URL from before
|
|
27
28
|
if (config && config.callbackURL) {
|
|
28
29
|
return config.callbackURL
|
|
@@ -42,15 +43,15 @@ const ssoCallbackUrl = async (config, type) => {
|
|
|
42
43
|
return `${publicConfig.platformUrl}${callbackUrl}`
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
export const googleCallbackUrl = async (config: any) => {
|
|
46
47
|
return ssoCallbackUrl(config, "google")
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
export const oidcCallbackUrl = async (config: any) => {
|
|
50
51
|
return ssoCallbackUrl(config, "oidc")
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
async function authInternal(ctx, user, err = null, info = null) {
|
|
54
|
+
async function authInternal(ctx: any, user: any, err = null, info = null) {
|
|
54
55
|
if (err) {
|
|
55
56
|
console.error("Authentication error", err)
|
|
56
57
|
return ctx.throw(403, info ? info : "Unauthorized")
|
|
@@ -71,20 +72,23 @@ async function authInternal(ctx, user, err = null, info = null) {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
return passport.authenticate(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
export const authenticate = async (ctx: any, next: any) => {
|
|
76
|
+
return passport.authenticate(
|
|
77
|
+
"local",
|
|
78
|
+
async (err: any, user: any, info: any) => {
|
|
79
|
+
await authInternal(ctx, user, err, info)
|
|
80
|
+
ctx.status = 200
|
|
81
|
+
}
|
|
82
|
+
)(ctx, next)
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
|
|
85
|
+
export const setInitInfo = (ctx: any) => {
|
|
82
86
|
const initInfo = ctx.request.body
|
|
83
87
|
setCookie(ctx, initInfo, Cookies.Init)
|
|
84
88
|
ctx.status = 200
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
export const getInitInfo = (ctx: any) => {
|
|
88
92
|
try {
|
|
89
93
|
ctx.body = getCookie(ctx, Cookies.Init) || {}
|
|
90
94
|
} catch (err) {
|
|
@@ -96,7 +100,7 @@ exports.getInitInfo = ctx => {
|
|
|
96
100
|
/**
|
|
97
101
|
* Reset the user password, used as part of a forgotten password flow.
|
|
98
102
|
*/
|
|
99
|
-
|
|
103
|
+
export const reset = async (ctx: any) => {
|
|
100
104
|
const { email } = ctx.request.body
|
|
101
105
|
const configured = await isEmailConfigured()
|
|
102
106
|
if (!configured) {
|
|
@@ -126,7 +130,7 @@ exports.reset = async ctx => {
|
|
|
126
130
|
/**
|
|
127
131
|
* Perform the user password update if the provided reset code is valid.
|
|
128
132
|
*/
|
|
129
|
-
|
|
133
|
+
export const resetUpdate = async (ctx: any) => {
|
|
130
134
|
const { resetCode, password } = ctx.request.body
|
|
131
135
|
try {
|
|
132
136
|
const { userId } = await checkResetPasswordCode(resetCode)
|
|
@@ -142,14 +146,14 @@ exports.resetUpdate = async ctx => {
|
|
|
142
146
|
}
|
|
143
147
|
}
|
|
144
148
|
|
|
145
|
-
|
|
149
|
+
export const logout = async (ctx: any) => {
|
|
146
150
|
if (ctx.user && ctx.user._id) {
|
|
147
151
|
await platformLogout({ ctx, userId: ctx.user._id })
|
|
148
152
|
}
|
|
149
153
|
ctx.body = { message: "User logged out." }
|
|
150
154
|
}
|
|
151
155
|
|
|
152
|
-
|
|
156
|
+
export const datasourcePreAuth = async (ctx: any, next: any) => {
|
|
153
157
|
const provider = ctx.params.provider
|
|
154
158
|
const middleware = require(`@budibase/backend-core/middleware`)
|
|
155
159
|
const handler = middleware.datasource[provider]
|
|
@@ -167,7 +171,7 @@ exports.datasourcePreAuth = async (ctx, next) => {
|
|
|
167
171
|
return handler.preAuth(passport, ctx, next)
|
|
168
172
|
}
|
|
169
173
|
|
|
170
|
-
|
|
174
|
+
export const datasourceAuth = async (ctx: any, next: any) => {
|
|
171
175
|
const authStateCookie = getCookie(ctx, Cookies.DatasourceAuth)
|
|
172
176
|
const provider = authStateCookie.provider
|
|
173
177
|
const middleware = require(`@budibase/backend-core/middleware`)
|
|
@@ -179,7 +183,7 @@ exports.datasourceAuth = async (ctx, next) => {
|
|
|
179
183
|
* The initial call that google authentication makes to take you to the google login screen.
|
|
180
184
|
* On a successful login, you will be redirected to the googleAuth callback route.
|
|
181
185
|
*/
|
|
182
|
-
|
|
186
|
+
export const googlePreAuth = async (ctx: any, next: any) => {
|
|
183
187
|
const db = getGlobalDB()
|
|
184
188
|
|
|
185
189
|
const config = await core.db.getScopedConfig(db, {
|
|
@@ -187,14 +191,14 @@ exports.googlePreAuth = async (ctx, next) => {
|
|
|
187
191
|
workspace: ctx.query.workspace,
|
|
188
192
|
})
|
|
189
193
|
let callbackUrl = await exports.googleCallbackUrl(config)
|
|
190
|
-
const strategy = await google.strategyFactory(config, callbackUrl)
|
|
194
|
+
const strategy = await google.strategyFactory(config, callbackUrl, users.save)
|
|
191
195
|
|
|
192
196
|
return passport.authenticate(strategy, {
|
|
193
197
|
scope: ["profile", "email"],
|
|
194
198
|
})(ctx, next)
|
|
195
199
|
}
|
|
196
200
|
|
|
197
|
-
|
|
201
|
+
export const googleAuth = async (ctx: any, next: any) => {
|
|
198
202
|
const db = getGlobalDB()
|
|
199
203
|
|
|
200
204
|
const config = await core.db.getScopedConfig(db, {
|
|
@@ -202,12 +206,12 @@ exports.googleAuth = async (ctx, next) => {
|
|
|
202
206
|
workspace: ctx.query.workspace,
|
|
203
207
|
})
|
|
204
208
|
const callbackUrl = await exports.googleCallbackUrl(config)
|
|
205
|
-
const strategy = await google.strategyFactory(config, callbackUrl)
|
|
209
|
+
const strategy = await google.strategyFactory(config, callbackUrl, users.save)
|
|
206
210
|
|
|
207
211
|
return passport.authenticate(
|
|
208
212
|
strategy,
|
|
209
213
|
{ successRedirect: "/", failureRedirect: "/error" },
|
|
210
|
-
async (err, user, info) => {
|
|
214
|
+
async (err: any, user: any, info: any) => {
|
|
211
215
|
await authInternal(ctx, user, err, info)
|
|
212
216
|
|
|
213
217
|
ctx.redirect("/")
|
|
@@ -215,24 +219,24 @@ exports.googleAuth = async (ctx, next) => {
|
|
|
215
219
|
)(ctx, next)
|
|
216
220
|
}
|
|
217
221
|
|
|
218
|
-
async function oidcStrategyFactory(ctx, configId) {
|
|
222
|
+
async function oidcStrategyFactory(ctx: any, configId: any) {
|
|
219
223
|
const db = getGlobalDB()
|
|
220
224
|
const config = await core.db.getScopedConfig(db, {
|
|
221
225
|
type: Configs.OIDC,
|
|
222
226
|
group: ctx.query.group,
|
|
223
227
|
})
|
|
224
228
|
|
|
225
|
-
const chosenConfig = config.configs.filter(c => c.uuid === configId)[0]
|
|
229
|
+
const chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
|
|
226
230
|
let callbackUrl = await exports.oidcCallbackUrl(chosenConfig)
|
|
227
231
|
|
|
228
|
-
return oidc.strategyFactory(chosenConfig, callbackUrl)
|
|
232
|
+
return oidc.strategyFactory(chosenConfig, callbackUrl, users.save)
|
|
229
233
|
}
|
|
230
234
|
|
|
231
235
|
/**
|
|
232
236
|
* The initial call that OIDC authentication makes to take you to the configured OIDC login screen.
|
|
233
237
|
* On a successful login, you will be redirected to the oidcAuth callback route.
|
|
234
238
|
*/
|
|
235
|
-
|
|
239
|
+
export const oidcPreAuth = async (ctx: any, next: any) => {
|
|
236
240
|
const { configId } = ctx.params
|
|
237
241
|
const strategy = await oidcStrategyFactory(ctx, configId)
|
|
238
242
|
|
|
@@ -244,14 +248,14 @@ exports.oidcPreAuth = async (ctx, next) => {
|
|
|
244
248
|
})(ctx, next)
|
|
245
249
|
}
|
|
246
250
|
|
|
247
|
-
|
|
251
|
+
export const oidcAuth = async (ctx: any, next: any) => {
|
|
248
252
|
const configId = getCookie(ctx, Cookies.OIDC_CONFIG)
|
|
249
253
|
const strategy = await oidcStrategyFactory(ctx, configId)
|
|
250
254
|
|
|
251
255
|
return passport.authenticate(
|
|
252
256
|
strategy,
|
|
253
257
|
{ successRedirect: "/", failureRedirect: "/error" },
|
|
254
|
-
async (err, user, info) => {
|
|
258
|
+
async (err: any, user: any, info: any) => {
|
|
255
259
|
await authInternal(ctx, user, err, info)
|
|
256
260
|
|
|
257
261
|
ctx.redirect("/")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { licensing, quotas } from "@budibase/pro"
|
|
2
|
+
|
|
3
|
+
export const activate = async (ctx: any) => {
|
|
4
|
+
const { licenseKey } = ctx.request.body
|
|
5
|
+
if (!licenseKey) {
|
|
6
|
+
ctx.throw(400, "licenseKey is required")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
await licensing.activateLicenseKey(licenseKey)
|
|
10
|
+
ctx.status = 200
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const refresh = async (ctx: any) => {
|
|
14
|
+
await licensing.cache.refresh()
|
|
15
|
+
ctx.status = 200
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const getInfo = async (ctx: any) => {
|
|
19
|
+
const licenseInfo = await licensing.getLicenseInfo()
|
|
20
|
+
if (licenseInfo) {
|
|
21
|
+
licenseInfo.licenseKey = "*"
|
|
22
|
+
ctx.body = licenseInfo
|
|
23
|
+
}
|
|
24
|
+
ctx.status = 200
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const getQuotaUsage = async (ctx: any) => {
|
|
28
|
+
const usage = await quotas.getQuotaUsage()
|
|
29
|
+
ctx.body = usage
|
|
30
|
+
}
|
|
@@ -15,6 +15,7 @@ const { encrypt } = require("@budibase/backend-core/encryption")
|
|
|
15
15
|
const { newid } = require("@budibase/backend-core/utils")
|
|
16
16
|
const { getUser } = require("../../utilities")
|
|
17
17
|
const { Cookies } = require("@budibase/backend-core/constants")
|
|
18
|
+
const { featureFlags } = require("@budibase/backend-core")
|
|
18
19
|
|
|
19
20
|
function newApiKey() {
|
|
20
21
|
return encrypt(`${getTenantId()}${SEPARATOR}${newid()}`)
|
|
@@ -68,6 +69,29 @@ const checkCurrentApp = ctx => {
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Add the attributes that are session based to the current user.
|
|
74
|
+
*/
|
|
75
|
+
const addSessionAttributesToUser = ctx => {
|
|
76
|
+
ctx.body.account = ctx.user.account
|
|
77
|
+
ctx.body.license = ctx.user.license
|
|
78
|
+
ctx.body.budibaseAccess = ctx.user.budibaseAccess
|
|
79
|
+
ctx.body.accountPortalAccess = ctx.user.accountPortalAccess
|
|
80
|
+
ctx.body.csrfToken = ctx.user.csrfToken
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Remove the attributes that are session based from the current user,
|
|
85
|
+
* so that stale values are not written to the db
|
|
86
|
+
*/
|
|
87
|
+
const removeSessionAttributesFromUser = ctx => {
|
|
88
|
+
delete ctx.request.body.csrfToken
|
|
89
|
+
delete ctx.request.body.account
|
|
90
|
+
delete ctx.request.body.accountPortalAccess
|
|
91
|
+
delete ctx.request.body.budibaseAccess
|
|
92
|
+
delete ctx.request.body.license
|
|
93
|
+
}
|
|
94
|
+
|
|
71
95
|
exports.getSelf = async ctx => {
|
|
72
96
|
if (!ctx.user) {
|
|
73
97
|
ctx.throw(403, "User not logged in")
|
|
@@ -81,11 +105,12 @@ exports.getSelf = async ctx => {
|
|
|
81
105
|
|
|
82
106
|
// get the main body of the user
|
|
83
107
|
ctx.body = await getUser(userId)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
ctx.body.
|
|
88
|
-
|
|
108
|
+
|
|
109
|
+
// add the feature flags for this tenant
|
|
110
|
+
const tenantId = getTenantId()
|
|
111
|
+
ctx.body.featureFlags = featureFlags.getTenantFeatureFlags(tenantId)
|
|
112
|
+
|
|
113
|
+
addSessionAttributesToUser(ctx)
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
exports.updateSelf = async ctx => {
|
|
@@ -104,8 +129,8 @@ exports.updateSelf = async ctx => {
|
|
|
104
129
|
// don't allow sending up an ID/Rev, always use the existing one
|
|
105
130
|
delete ctx.request.body._id
|
|
106
131
|
delete ctx.request.body._rev
|
|
107
|
-
|
|
108
|
-
|
|
132
|
+
removeSessionAttributesFromUser(ctx)
|
|
133
|
+
|
|
109
134
|
const response = await db.put({
|
|
110
135
|
...user,
|
|
111
136
|
...ctx.request.body,
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
const {
|
|
2
2
|
getGlobalUserParams,
|
|
3
3
|
StaticDatabases,
|
|
4
|
-
generateNewUsageQuotaDoc,
|
|
5
4
|
} = require("@budibase/backend-core/db")
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
const { EmailTemplatePurpose } = require("../../../constants")
|
|
11
|
-
const { checkInviteCode } = require("../../../utilities/redis")
|
|
12
|
-
const { sendEmail } = require("../../../utilities/email")
|
|
5
|
+
const { getGlobalUserByEmail } = require("@budibase/backend-core/utils")
|
|
6
|
+
import { EmailTemplatePurpose } from "../../../constants"
|
|
7
|
+
import { checkInviteCode } from "../../../utilities/redis"
|
|
8
|
+
import { sendEmail } from "../../../utilities/email"
|
|
13
9
|
const { user: userCache } = require("@budibase/backend-core/cache")
|
|
14
10
|
const { invalidateSessions } = require("@budibase/backend-core/sessions")
|
|
15
11
|
const accounts = require("@budibase/backend-core/accounts")
|
|
@@ -21,26 +17,28 @@ const {
|
|
|
21
17
|
doesTenantExist,
|
|
22
18
|
} = require("@budibase/backend-core/tenancy")
|
|
23
19
|
const { removeUserFromInfoDB } = require("@budibase/backend-core/deprovision")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
import env from "../../../environment"
|
|
21
|
+
import { syncUserInApps } from "../../../utilities/appService"
|
|
22
|
+
import { quotas, users } from "@budibase/pro"
|
|
23
|
+
const { errors } = require("@budibase/backend-core")
|
|
24
|
+
import { allUsers, getUser } from "../../utilities"
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
export const save = async (ctx: any) => {
|
|
29
27
|
try {
|
|
30
|
-
const user = await
|
|
28
|
+
const user: any = await users.save(ctx.request.body, getTenantId())
|
|
31
29
|
// let server know to sync user
|
|
32
30
|
await syncUserInApps(user._id)
|
|
33
31
|
ctx.body = user
|
|
34
|
-
} catch (err) {
|
|
32
|
+
} catch (err: any) {
|
|
35
33
|
ctx.throw(err.status || 400, err)
|
|
36
34
|
}
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
const parseBooleanParam = param => {
|
|
37
|
+
const parseBooleanParam = (param: any) => {
|
|
40
38
|
return !(param && param === "false")
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
export const adminUser = async (ctx: any) => {
|
|
44
42
|
const { email, password, tenantId } = ctx.request.body
|
|
45
43
|
|
|
46
44
|
// account portal sends a pre-hashed password - honour param to prevent double hashing
|
|
@@ -52,7 +50,7 @@ exports.adminUser = async ctx => {
|
|
|
52
50
|
ctx.throw(403, "Organisation already exists.")
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
const response = await doWithGlobalDB(tenantId, async db => {
|
|
53
|
+
const response = await doWithGlobalDB(tenantId, async (db: any) => {
|
|
56
54
|
const response = await db.allDocs(
|
|
57
55
|
getGlobalUserParams(null, {
|
|
58
56
|
include_docs: true,
|
|
@@ -69,12 +67,12 @@ exports.adminUser = async ctx => {
|
|
|
69
67
|
} catch (err) {
|
|
70
68
|
// don't worry about errors
|
|
71
69
|
}
|
|
72
|
-
await db.put(
|
|
70
|
+
await db.put(quotas.generateNewQuotaUsage())
|
|
73
71
|
}
|
|
74
72
|
return response
|
|
75
73
|
})
|
|
76
74
|
|
|
77
|
-
if (response.rows.some(row => row.doc.admin)) {
|
|
75
|
+
if (response.rows.some((row: any) => row.doc.admin)) {
|
|
78
76
|
ctx.throw(
|
|
79
77
|
403,
|
|
80
78
|
"You cannot initialise once an global user has been created."
|
|
@@ -95,13 +93,13 @@ exports.adminUser = async ctx => {
|
|
|
95
93
|
tenantId,
|
|
96
94
|
}
|
|
97
95
|
try {
|
|
98
|
-
ctx.body = await
|
|
99
|
-
} catch (err) {
|
|
96
|
+
ctx.body = await users.save(user, tenantId, hashPassword, requirePassword)
|
|
97
|
+
} catch (err: any) {
|
|
100
98
|
ctx.throw(err.status || 400, err)
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
export const destroy = async (ctx: any) => {
|
|
105
103
|
const db = getGlobalDB()
|
|
106
104
|
const dbUser = await db.get(ctx.params.id)
|
|
107
105
|
|
|
@@ -120,6 +118,7 @@ exports.destroy = async ctx => {
|
|
|
120
118
|
|
|
121
119
|
await removeUserFromInfoDB(dbUser)
|
|
122
120
|
await db.remove(dbUser._id, dbUser._rev)
|
|
121
|
+
await quotas.removeUser(dbUser)
|
|
123
122
|
await userCache.invalidateUser(dbUser._id)
|
|
124
123
|
await invalidateSessions(dbUser._id)
|
|
125
124
|
// let server know to sync user
|
|
@@ -130,23 +129,23 @@ exports.destroy = async ctx => {
|
|
|
130
129
|
}
|
|
131
130
|
|
|
132
131
|
// called internally by app server user fetch
|
|
133
|
-
|
|
134
|
-
const
|
|
132
|
+
export const fetch = async (ctx: any) => {
|
|
133
|
+
const all = await allUsers()
|
|
135
134
|
// user hashed password shouldn't ever be returned
|
|
136
|
-
for (let user of
|
|
135
|
+
for (let user of all) {
|
|
137
136
|
if (user) {
|
|
138
137
|
delete user.password
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
|
-
ctx.body =
|
|
140
|
+
ctx.body = all
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
// called internally by app server user find
|
|
145
|
-
|
|
144
|
+
export const find = async (ctx: any) => {
|
|
146
145
|
ctx.body = await getUser(ctx.params.id)
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
|
|
148
|
+
export const tenantUserLookup = async (ctx: any) => {
|
|
150
149
|
const id = ctx.params.id
|
|
151
150
|
const user = await getTenantUser(id)
|
|
152
151
|
if (user) {
|
|
@@ -156,7 +155,7 @@ exports.tenantUserLookup = async ctx => {
|
|
|
156
155
|
}
|
|
157
156
|
}
|
|
158
157
|
|
|
159
|
-
|
|
158
|
+
export const invite = async (ctx: any) => {
|
|
160
159
|
let { email, userInfo } = ctx.request.body
|
|
161
160
|
const existing = await getGlobalUserByEmail(email)
|
|
162
161
|
if (existing) {
|
|
@@ -166,21 +165,22 @@ exports.invite = async ctx => {
|
|
|
166
165
|
userInfo = {}
|
|
167
166
|
}
|
|
168
167
|
userInfo.tenantId = getTenantId()
|
|
169
|
-
|
|
168
|
+
const opts: any = {
|
|
170
169
|
subject: "{{ company }} platform invitation",
|
|
171
170
|
info: userInfo,
|
|
172
|
-
}
|
|
171
|
+
}
|
|
172
|
+
await sendEmail(email, EmailTemplatePurpose.INVITATION, opts)
|
|
173
173
|
ctx.body = {
|
|
174
174
|
message: "Invitation has been sent.",
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
export const inviteAccept = async (ctx: any) => {
|
|
179
179
|
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
|
180
180
|
try {
|
|
181
181
|
// info is an extension of the user object that was stored by global
|
|
182
|
-
const { email, info } = await checkInviteCode(inviteCode)
|
|
183
|
-
ctx.body = await
|
|
182
|
+
const { email, info }: any = await checkInviteCode(inviteCode)
|
|
183
|
+
ctx.body = await users.save(
|
|
184
184
|
{
|
|
185
185
|
firstName,
|
|
186
186
|
lastName,
|
|
@@ -190,7 +190,11 @@ exports.inviteAccept = async ctx => {
|
|
|
190
190
|
},
|
|
191
191
|
info.tenantId
|
|
192
192
|
)
|
|
193
|
-
} catch (err) {
|
|
193
|
+
} catch (err: any) {
|
|
194
|
+
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
|
195
|
+
// explicitly re-throw limit exceeded errors
|
|
196
|
+
ctx.throw(400, err)
|
|
197
|
+
}
|
|
194
198
|
ctx.throw(400, "Unable to create new user, invitation invalid.")
|
|
195
199
|
}
|
|
196
200
|
}
|
package/src/api/index.js
CHANGED
|
@@ -8,6 +8,8 @@ const {
|
|
|
8
8
|
buildTenancyMiddleware,
|
|
9
9
|
buildCsrfMiddleware,
|
|
10
10
|
} = require("@budibase/backend-core/auth")
|
|
11
|
+
const { middleware: pro } = require("@budibase/pro")
|
|
12
|
+
const { errors } = require("@budibase/backend-core")
|
|
11
13
|
|
|
12
14
|
const PUBLIC_ENDPOINTS = [
|
|
13
15
|
// old deprecated endpoints kept for backwards compat
|
|
@@ -98,6 +100,7 @@ router
|
|
|
98
100
|
.use(buildAuthMiddleware(PUBLIC_ENDPOINTS))
|
|
99
101
|
.use(buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_ENDPOINTS))
|
|
100
102
|
.use(buildCsrfMiddleware({ noCsrfPatterns: NO_CSRF_ENDPOINTS }))
|
|
103
|
+
.use(pro.licensing())
|
|
101
104
|
// for now no public access is allowed to worker (bar health check)
|
|
102
105
|
.use((ctx, next) => {
|
|
103
106
|
if (ctx.publicEndpoint) {
|
|
@@ -110,16 +113,18 @@ router
|
|
|
110
113
|
})
|
|
111
114
|
.use(auditLog)
|
|
112
115
|
|
|
113
|
-
// error handling middleware
|
|
116
|
+
// error handling middleware - TODO: This could be moved to backend-core
|
|
114
117
|
router.use(async (ctx, next) => {
|
|
115
118
|
try {
|
|
116
119
|
await next()
|
|
117
120
|
} catch (err) {
|
|
118
121
|
ctx.log.error(err)
|
|
119
122
|
ctx.status = err.status || err.statusCode || 500
|
|
123
|
+
const error = errors.getPublicError(err)
|
|
120
124
|
ctx.body = {
|
|
121
125
|
message: err.message,
|
|
122
126
|
status: ctx.status,
|
|
127
|
+
error,
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
130
|
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Router from "@koa/router"
|
|
2
|
+
import * as controller from "../../controllers/global/license"
|
|
3
|
+
|
|
4
|
+
const router = new Router()
|
|
5
|
+
|
|
6
|
+
router
|
|
7
|
+
.post("/api/global/license/activate", controller.activate)
|
|
8
|
+
.post("/api/global/license/refresh", controller.refresh)
|
|
9
|
+
.get("/api/global/license/info", controller.getInfo)
|
|
10
|
+
.get("/api/global/license/usage", controller.getQuotaUsage)
|
|
11
|
+
|
|
12
|
+
export = router
|
package/src/api/routes/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const environmentRoutes = require("./system/environment")
|
|
|
10
10
|
const tenantsRoutes = require("./system/tenants")
|
|
11
11
|
const statusRoutes = require("./system/status")
|
|
12
12
|
const selfRoutes = require("./global/self")
|
|
13
|
+
const licenseRoutes = require("./global/license")
|
|
13
14
|
|
|
14
15
|
exports.routes = [
|
|
15
16
|
configRoutes,
|
|
@@ -24,4 +25,5 @@ exports.routes = [
|
|
|
24
25
|
environmentRoutes,
|
|
25
26
|
statusRoutes,
|
|
26
27
|
selfRoutes,
|
|
28
|
+
licenseRoutes,
|
|
27
29
|
]
|
|
@@ -75,7 +75,8 @@ describe("/api/global/auth", () => {
|
|
|
75
75
|
afterEach(() => {
|
|
76
76
|
expect(strategyFactory).toBeCalledWith(
|
|
77
77
|
chosenConfig,
|
|
78
|
-
`http://localhost:10000/api/global/auth/${TENANT_ID}/oidc/callback
|
|
78
|
+
`http://localhost:10000/api/global/auth/${TENANT_ID}/oidc/callback`,
|
|
79
|
+
expect.any(Function)
|
|
79
80
|
)
|
|
80
81
|
})
|
|
81
82
|
|
package/src/environment.js
CHANGED
|
@@ -19,7 +19,7 @@ if (!LOADED && isDev() && !isTest()) {
|
|
|
19
19
|
module.exports = {
|
|
20
20
|
NODE_ENV: process.env.NODE_ENV,
|
|
21
21
|
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
|
22
|
-
PORT: process.env.PORT,
|
|
22
|
+
PORT: process.env.PORT || process.env.WORKER_PORT,
|
|
23
23
|
CLUSTER_PORT: process.env.CLUSTER_PORT,
|
|
24
24
|
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
|
|
25
25
|
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Scope } from "@sentry/node"
|
|
3
3
|
import { Event } from "@sentry/types/dist/event"
|
|
4
4
|
import Application from "koa"
|
|
5
|
+
import { bootstrap } from "global-agent"
|
|
5
6
|
|
|
6
7
|
const env = require("./environment")
|
|
7
8
|
import db from "./db"
|
|
@@ -17,6 +18,9 @@ const api = require("./api")
|
|
|
17
18
|
const redis = require("./utilities/redis")
|
|
18
19
|
const Sentry = require("@sentry/node")
|
|
19
20
|
|
|
21
|
+
// this will setup http and https proxies form env variables
|
|
22
|
+
bootstrap()
|
|
23
|
+
|
|
20
24
|
const app: Application = new Koa()
|
|
21
25
|
|
|
22
26
|
app.keys = ["secret", "key"]
|