@budibase/worker 1.0.219 → 1.0.220-alpha.2
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/.yarnrc +1 -0
- package/package.json +6 -6
- package/src/api/controllers/global/auth.ts +14 -34
- package/src/api/controllers/global/configs.js +5 -9
- package/src/api/controllers/global/roles.js +3 -3
- package/src/api/controllers/global/users.ts +18 -13
- package/src/api/routes/global/users.js +1 -0
- package/src/api/routes/tests/auth.spec.js +10 -7
- package/src/sdk/users/users.ts +33 -3
- package/src/utilities/users.js +17 -0
package/.yarnrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
network-timeout 100000
|
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.220-alpha.2",
|
|
5
5
|
"description": "Budibase background service",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"author": "Budibase",
|
|
35
35
|
"license": "GPL-3.0",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@budibase/backend-core": "^1.0.
|
|
38
|
-
"@budibase/pro": "1.0.
|
|
39
|
-
"@budibase/string-templates": "^1.0.
|
|
37
|
+
"@budibase/backend-core": "^1.0.220-alpha.2",
|
|
38
|
+
"@budibase/pro": "1.0.220-alpha.1",
|
|
39
|
+
"@budibase/string-templates": "^1.0.220-alpha.2",
|
|
40
40
|
"@koa/router": "8.0.8",
|
|
41
41
|
"@sentry/node": "6.17.7",
|
|
42
42
|
"@techpass/passport-openidconnect": "0.3.2",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"server-destroy": "1.0.1"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@budibase/types": "^1.0.
|
|
69
|
+
"@budibase/types": "^1.0.220-alpha.2",
|
|
70
70
|
"@types/jest": "26.0.23",
|
|
71
71
|
"@types/koa": "2.13.4",
|
|
72
72
|
"@types/koa-router": "7.4.4",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"./scripts/jestSetup.js"
|
|
101
101
|
]
|
|
102
102
|
},
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "80b7a2da2fba39ff25622873c95598fa931ea6ed"
|
|
104
104
|
}
|
|
@@ -1,49 +1,22 @@
|
|
|
1
1
|
const core = require("@budibase/backend-core")
|
|
2
|
-
const { getScopedConfig } = require("@budibase/backend-core/db")
|
|
3
|
-
const { google } = require("@budibase/backend-core/middleware")
|
|
4
|
-
const { oidc } = require("@budibase/backend-core/middleware")
|
|
5
2
|
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
|
6
3
|
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
|
7
4
|
const { setCookie, getCookie, clearCookie, hash, platformLogout } = core.utils
|
|
8
5
|
const { Cookies, Headers } = core.constants
|
|
9
|
-
const { passport } = core.auth
|
|
6
|
+
const { passport, ssoCallbackUrl, google, oidc } = core.auth
|
|
10
7
|
const { checkResetPasswordCode } = require("../../../utilities/redis")
|
|
11
|
-
const {
|
|
12
|
-
getGlobalDB,
|
|
13
|
-
getTenantId,
|
|
14
|
-
isMultiTenant,
|
|
15
|
-
} = require("@budibase/backend-core/tenancy")
|
|
8
|
+
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
|
16
9
|
const env = require("../../../environment")
|
|
17
10
|
import { events, users as usersCore, context } from "@budibase/backend-core"
|
|
18
11
|
import { users } from "../../../sdk"
|
|
19
12
|
import { User } from "@budibase/types"
|
|
20
13
|
|
|
21
|
-
const ssoCallbackUrl = async (config: any, type: any) => {
|
|
22
|
-
// incase there is a callback URL from before
|
|
23
|
-
if (config && config.callbackURL) {
|
|
24
|
-
return config.callbackURL
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const db = getGlobalDB()
|
|
28
|
-
const publicConfig = await getScopedConfig(db, {
|
|
29
|
-
type: Configs.SETTINGS,
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
let callbackUrl = `/api/global/auth`
|
|
33
|
-
if (isMultiTenant()) {
|
|
34
|
-
callbackUrl += `/${getTenantId()}`
|
|
35
|
-
}
|
|
36
|
-
callbackUrl += `/${type}/callback`
|
|
37
|
-
|
|
38
|
-
return `${publicConfig.platformUrl}${callbackUrl}`
|
|
39
|
-
}
|
|
40
|
-
|
|
41
14
|
export const googleCallbackUrl = async (config: any) => {
|
|
42
|
-
return ssoCallbackUrl(config, "google")
|
|
15
|
+
return ssoCallbackUrl(getGlobalDB(), config, "google")
|
|
43
16
|
}
|
|
44
17
|
|
|
45
18
|
export const oidcCallbackUrl = async (config: any) => {
|
|
46
|
-
return ssoCallbackUrl(config, "oidc")
|
|
19
|
+
return ssoCallbackUrl(getGlobalDB(), config, "oidc")
|
|
47
20
|
}
|
|
48
21
|
|
|
49
22
|
async function authInternal(ctx: any, user: any, err = null, info = null) {
|
|
@@ -198,6 +171,8 @@ export const googlePreAuth = async (ctx: any, next: any) => {
|
|
|
198
171
|
|
|
199
172
|
return passport.authenticate(strategy, {
|
|
200
173
|
scope: ["profile", "email"],
|
|
174
|
+
accessType: "offline",
|
|
175
|
+
prompt: "consent",
|
|
201
176
|
})(ctx, next)
|
|
202
177
|
}
|
|
203
178
|
|
|
@@ -224,7 +199,7 @@ export const googleAuth = async (ctx: any, next: any) => {
|
|
|
224
199
|
)(ctx, next)
|
|
225
200
|
}
|
|
226
201
|
|
|
227
|
-
|
|
202
|
+
export const oidcStrategyFactory = async (ctx: any, configId: any) => {
|
|
228
203
|
const db = getGlobalDB()
|
|
229
204
|
const config = await core.db.getScopedConfig(db, {
|
|
230
205
|
type: Configs.OIDC,
|
|
@@ -234,7 +209,12 @@ async function oidcStrategyFactory(ctx: any, configId: any) {
|
|
|
234
209
|
const chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
|
|
235
210
|
let callbackUrl = await exports.oidcCallbackUrl(chosenConfig)
|
|
236
211
|
|
|
237
|
-
|
|
212
|
+
//Remote Config
|
|
213
|
+
const enrichedConfig = await oidc.fetchStrategyConfig(
|
|
214
|
+
chosenConfig,
|
|
215
|
+
callbackUrl
|
|
216
|
+
)
|
|
217
|
+
return oidc.strategyFactory(enrichedConfig, users.save)
|
|
238
218
|
}
|
|
239
219
|
|
|
240
220
|
/**
|
|
@@ -249,7 +229,7 @@ export const oidcPreAuth = async (ctx: any, next: any) => {
|
|
|
249
229
|
|
|
250
230
|
return passport.authenticate(strategy, {
|
|
251
231
|
// required 'openid' scope is added by oidc strategy factory
|
|
252
|
-
scope: ["profile", "email"],
|
|
232
|
+
scope: ["profile", "email", "offline_access"], //auth0 offline_access scope required for the refresh token behaviour.
|
|
253
233
|
})(ctx, next)
|
|
254
234
|
}
|
|
255
235
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const {
|
|
2
2
|
generateConfigID,
|
|
3
3
|
getConfigParams,
|
|
4
|
-
getGlobalUserParams,
|
|
5
4
|
getScopedFullConfig,
|
|
6
5
|
getAllApps,
|
|
7
6
|
} = require("@budibase/backend-core/db")
|
|
@@ -18,8 +17,10 @@ const {
|
|
|
18
17
|
withCache,
|
|
19
18
|
CacheKeys,
|
|
20
19
|
bustCache,
|
|
20
|
+
cache,
|
|
21
21
|
} = require("@budibase/backend-core/cache")
|
|
22
22
|
const { events } = require("@budibase/backend-core")
|
|
23
|
+
const { checkAnyUserExists } = require("../../../utilities/users")
|
|
23
24
|
|
|
24
25
|
const BB_TENANT_CDN = "https://tenants.cdn.budi.live"
|
|
25
26
|
|
|
@@ -365,9 +366,9 @@ exports.upload = async function (ctx) {
|
|
|
365
366
|
exports.destroy = async function (ctx) {
|
|
366
367
|
const db = getGlobalDB()
|
|
367
368
|
const { id, rev } = ctx.params
|
|
368
|
-
|
|
369
369
|
try {
|
|
370
370
|
await db.remove(id, rev)
|
|
371
|
+
await cache.delete(CacheKeys.CHECKLIST)
|
|
371
372
|
ctx.body = { message: "Config deleted successfully" }
|
|
372
373
|
} catch (err) {
|
|
373
374
|
ctx.throw(err.status, err)
|
|
@@ -405,12 +406,7 @@ exports.configChecklist = async function (ctx) {
|
|
|
405
406
|
})
|
|
406
407
|
|
|
407
408
|
// They have set up an global user
|
|
408
|
-
const
|
|
409
|
-
getGlobalUserParams(null, {
|
|
410
|
-
include_docs: true,
|
|
411
|
-
limit: 1,
|
|
412
|
-
})
|
|
413
|
-
)
|
|
409
|
+
const userExists = await checkAnyUserExists()
|
|
414
410
|
return {
|
|
415
411
|
apps: {
|
|
416
412
|
checked: apps.length > 0,
|
|
@@ -423,7 +419,7 @@ exports.configChecklist = async function (ctx) {
|
|
|
423
419
|
link: "/builder/portal/manage/email",
|
|
424
420
|
},
|
|
425
421
|
adminUser: {
|
|
426
|
-
checked:
|
|
422
|
+
checked: userExists,
|
|
427
423
|
label: "Create your first user",
|
|
428
424
|
link: "/builder/portal/manage/users",
|
|
429
425
|
},
|
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
|
8
8
|
const { user: userCache } = require("@budibase/backend-core/cache")
|
|
9
9
|
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
|
10
|
-
const {
|
|
10
|
+
const { allUsers } = require("../../../sdk/users")
|
|
11
11
|
|
|
12
12
|
exports.fetch = async ctx => {
|
|
13
13
|
const tenantId = ctx.user.tenantId
|
|
@@ -49,10 +49,10 @@ exports.find = async ctx => {
|
|
|
49
49
|
exports.removeAppRole = async ctx => {
|
|
50
50
|
const { appId } = ctx.params
|
|
51
51
|
const db = getGlobalDB()
|
|
52
|
-
const
|
|
52
|
+
const users = await allUsers(ctx)
|
|
53
53
|
const bulk = []
|
|
54
54
|
const cacheInvalidations = []
|
|
55
|
-
for (let user of
|
|
55
|
+
for (let user of users) {
|
|
56
56
|
if (user.roles[appId]) {
|
|
57
57
|
cacheInvalidations.push(userCache.invalidateUser(user._id))
|
|
58
58
|
delete user.roles[appId]
|
|
@@ -8,16 +8,15 @@ import {
|
|
|
8
8
|
events,
|
|
9
9
|
errors,
|
|
10
10
|
accounts,
|
|
11
|
-
db as dbUtils,
|
|
12
11
|
users as usersCore,
|
|
13
12
|
tenancy,
|
|
14
13
|
cache,
|
|
15
14
|
} from "@budibase/backend-core"
|
|
15
|
+
import { checkAnyUserExists } from "../../../utilities/users"
|
|
16
16
|
|
|
17
17
|
export const save = async (ctx: any) => {
|
|
18
18
|
try {
|
|
19
|
-
|
|
20
|
-
ctx.body = user
|
|
19
|
+
ctx.body = await users.save(ctx.request.body)
|
|
21
20
|
} catch (err: any) {
|
|
22
21
|
ctx.throw(err.status || 400, err)
|
|
23
22
|
}
|
|
@@ -39,15 +38,8 @@ export const adminUser = async (ctx: any) => {
|
|
|
39
38
|
ctx.throw(403, "Organisation already exists.")
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
dbUtils.getGlobalUserParams(null, {
|
|
45
|
-
include_docs: true,
|
|
46
|
-
})
|
|
47
|
-
)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
if (response.rows.some((row: any) => row.doc.admin)) {
|
|
41
|
+
const userExists = await checkAnyUserExists()
|
|
42
|
+
if (userExists) {
|
|
51
43
|
ctx.throw(
|
|
52
44
|
403,
|
|
53
45
|
"You cannot initialise once an global user has been created."
|
|
@@ -68,11 +60,13 @@ export const adminUser = async (ctx: any) => {
|
|
|
68
60
|
tenantId,
|
|
69
61
|
}
|
|
70
62
|
try {
|
|
63
|
+
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
|
64
|
+
// stuck in a cycle
|
|
65
|
+
await cache.bustCache(cache.CacheKeys.CHECKLIST)
|
|
71
66
|
const finalUser = await users.save(user, {
|
|
72
67
|
hashPassword,
|
|
73
68
|
requirePassword,
|
|
74
69
|
})
|
|
75
|
-
await cache.bustCache(cache.CacheKeys.CHECKLIST)
|
|
76
70
|
|
|
77
71
|
// events
|
|
78
72
|
let account: CloudAccount | undefined
|
|
@@ -96,6 +90,17 @@ export const destroy = async (ctx: any) => {
|
|
|
96
90
|
}
|
|
97
91
|
}
|
|
98
92
|
|
|
93
|
+
export const search = async (ctx: any) => {
|
|
94
|
+
const paginated = await users.paginatedUsers(ctx.request.body)
|
|
95
|
+
// user hashed password shouldn't ever be returned
|
|
96
|
+
for (let user of paginated.data) {
|
|
97
|
+
if (user) {
|
|
98
|
+
delete user.password
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
ctx.body = paginated
|
|
102
|
+
}
|
|
103
|
+
|
|
99
104
|
// called internally by app server user fetch
|
|
100
105
|
export const fetch = async (ctx: any) => {
|
|
101
106
|
const all = await users.allUsers()
|
|
@@ -46,6 +46,7 @@ router
|
|
|
46
46
|
controller.save
|
|
47
47
|
)
|
|
48
48
|
.get("/api/global/users", builderOrAdmin, controller.fetch)
|
|
49
|
+
.post("/api/global/users/search", builderOrAdmin, controller.search)
|
|
49
50
|
.delete("/api/global/users/:id", adminOnly, controller.destroy)
|
|
50
51
|
.get("/api/global/roles/:appId")
|
|
51
52
|
.post(
|
|
@@ -77,27 +77,30 @@ describe("/api/global/auth", () => {
|
|
|
77
77
|
describe("oidc", () => {
|
|
78
78
|
const auth = require("@budibase/backend-core/auth")
|
|
79
79
|
|
|
80
|
+
const passportSpy = jest.spyOn(auth.passport, "authenticate")
|
|
81
|
+
let oidcConf
|
|
82
|
+
let chosenConfig
|
|
83
|
+
let configId
|
|
84
|
+
|
|
80
85
|
// mock the oidc strategy implementation and return value
|
|
81
86
|
let strategyFactory = jest.fn()
|
|
82
87
|
let mockStrategyReturn = jest.fn()
|
|
88
|
+
let mockStrategyConfig = jest.fn()
|
|
89
|
+
auth.oidc.fetchStrategyConfig = mockStrategyConfig
|
|
90
|
+
|
|
83
91
|
strategyFactory.mockReturnValue(mockStrategyReturn)
|
|
84
92
|
auth.oidc.strategyFactory = strategyFactory
|
|
85
93
|
|
|
86
|
-
const passportSpy = jest.spyOn(auth.passport, "authenticate")
|
|
87
|
-
let oidcConf
|
|
88
|
-
let chosenConfig
|
|
89
|
-
let configId
|
|
90
|
-
|
|
91
94
|
beforeEach(async () => {
|
|
92
95
|
oidcConf = await config.saveOIDCConfig()
|
|
93
96
|
chosenConfig = oidcConf.config.configs[0]
|
|
94
97
|
configId = chosenConfig.uuid
|
|
98
|
+
mockStrategyConfig.mockReturnValue(chosenConfig)
|
|
95
99
|
})
|
|
96
100
|
|
|
97
101
|
afterEach(() => {
|
|
98
102
|
expect(strategyFactory).toBeCalledWith(
|
|
99
103
|
chosenConfig,
|
|
100
|
-
`http://localhost:10000/api/global/auth/${TENANT_ID}/oidc/callback`,
|
|
101
104
|
expect.any(Function)
|
|
102
105
|
)
|
|
103
106
|
})
|
|
@@ -107,7 +110,7 @@ describe("/api/global/auth", () => {
|
|
|
107
110
|
await request.get(`/api/global/auth/${TENANT_ID}/oidc/configs/${configId}`)
|
|
108
111
|
|
|
109
112
|
expect(passportSpy).toBeCalledWith(mockStrategyReturn, {
|
|
110
|
-
scope: ["profile", "email"]
|
|
113
|
+
scope: ["profile", "email", "offline_access"]
|
|
111
114
|
})
|
|
112
115
|
expect(passportSpy.mock.calls.length).toBe(1);
|
|
113
116
|
})
|
package/src/sdk/users/users.ts
CHANGED
|
@@ -17,9 +17,8 @@ import {
|
|
|
17
17
|
} from "@budibase/backend-core"
|
|
18
18
|
import { MigrationType } from "@budibase/types"
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*/
|
|
20
|
+
const PAGE_LIMIT = 8
|
|
21
|
+
|
|
23
22
|
export const allUsers = async () => {
|
|
24
23
|
const db = tenancy.getGlobalDB()
|
|
25
24
|
const response = await db.allDocs(
|
|
@@ -30,6 +29,37 @@ export const allUsers = async () => {
|
|
|
30
29
|
return response.rows.map((row: any) => row.doc)
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
export const paginatedUsers = async ({
|
|
33
|
+
page,
|
|
34
|
+
search,
|
|
35
|
+
}: { page?: string; search?: string } = {}) => {
|
|
36
|
+
const db = tenancy.getGlobalDB()
|
|
37
|
+
// get one extra document, to have the next page
|
|
38
|
+
const opts: any = {
|
|
39
|
+
include_docs: true,
|
|
40
|
+
limit: PAGE_LIMIT + 1,
|
|
41
|
+
}
|
|
42
|
+
// add a startkey if the page was specified (anchor)
|
|
43
|
+
if (page) {
|
|
44
|
+
opts.startkey = page
|
|
45
|
+
}
|
|
46
|
+
// property specifies what to use for the page/anchor
|
|
47
|
+
let userList, property
|
|
48
|
+
// no search, query allDocs
|
|
49
|
+
if (!search) {
|
|
50
|
+
const response = await db.allDocs(dbUtils.getGlobalUserParams(null, opts))
|
|
51
|
+
userList = response.rows.map((row: any) => row.doc)
|
|
52
|
+
property = "_id"
|
|
53
|
+
} else {
|
|
54
|
+
userList = await usersCore.searchGlobalUsersByEmail(search, opts)
|
|
55
|
+
property = "email"
|
|
56
|
+
}
|
|
57
|
+
return dbUtils.pagination(userList, PAGE_LIMIT, {
|
|
58
|
+
paginate: true,
|
|
59
|
+
property,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
33
63
|
/**
|
|
34
64
|
* Gets a user by ID from the global database, based on the current tenancy.
|
|
35
65
|
*/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
|
2
|
+
const { getGlobalUserParams } = require("@budibase/backend-core/db")
|
|
3
|
+
|
|
4
|
+
exports.checkAnyUserExists = async () => {
|
|
5
|
+
try {
|
|
6
|
+
const db = getGlobalDB()
|
|
7
|
+
const users = await db.allDocs(
|
|
8
|
+
getGlobalUserParams(null, {
|
|
9
|
+
include_docs: true,
|
|
10
|
+
limit: 1,
|
|
11
|
+
})
|
|
12
|
+
)
|
|
13
|
+
return users && users.rows.length >= 1
|
|
14
|
+
} catch (err) {
|
|
15
|
+
throw new Error("Unable to retrieve user list")
|
|
16
|
+
}
|
|
17
|
+
}
|