@budibase/backend-core 2.9.19 → 2.9.20
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/dist/package.json +19 -4
- package/dist/src/security/permissions.d.ts +1 -1
- package/package.json +19 -4
- package/dist/tsconfig.build.tsbuildinfo +0 -1
- package/src/accounts/accounts.ts +0 -82
- package/src/accounts/api.ts +0 -59
- package/src/accounts/index.ts +0 -1
- package/src/auth/auth.ts +0 -208
- package/src/auth/index.ts +0 -1
- package/src/auth/tests/auth.spec.ts +0 -14
- package/src/blacklist/blacklist.ts +0 -54
- package/src/blacklist/index.ts +0 -1
- package/src/blacklist/tests/blacklist.spec.ts +0 -46
- package/src/cache/appMetadata.ts +0 -88
- package/src/cache/base/index.ts +0 -92
- package/src/cache/generic.ts +0 -30
- package/src/cache/index.ts +0 -5
- package/src/cache/tests/writethrough.spec.ts +0 -138
- package/src/cache/user.ts +0 -69
- package/src/cache/writethrough.ts +0 -133
- package/src/configs/configs.ts +0 -257
- package/src/configs/index.ts +0 -1
- package/src/configs/tests/configs.spec.ts +0 -184
- package/src/constants/db.ts +0 -63
- package/src/constants/index.ts +0 -2
- package/src/constants/misc.ts +0 -50
- package/src/context/Context.ts +0 -14
- package/src/context/identity.ts +0 -58
- package/src/context/index.ts +0 -3
- package/src/context/mainContext.ts +0 -310
- package/src/context/tests/index.spec.ts +0 -147
- package/src/context/types.ts +0 -11
- package/src/db/Replication.ts +0 -84
- package/src/db/constants.ts +0 -10
- package/src/db/couch/DatabaseImpl.ts +0 -238
- package/src/db/couch/connections.ts +0 -77
- package/src/db/couch/index.ts +0 -5
- package/src/db/couch/pouchDB.ts +0 -97
- package/src/db/couch/pouchDump.ts +0 -0
- package/src/db/couch/utils.ts +0 -50
- package/src/db/db.ts +0 -39
- package/src/db/errors.ts +0 -14
- package/src/db/index.ts +0 -12
- package/src/db/lucene.ts +0 -732
- package/src/db/searchIndexes/index.ts +0 -1
- package/src/db/searchIndexes/searchIndexes.ts +0 -62
- package/src/db/tests/index.spec.js +0 -25
- package/src/db/tests/lucene.spec.ts +0 -298
- package/src/db/tests/pouch.spec.js +0 -62
- package/src/db/tests/utils.spec.ts +0 -63
- package/src/db/utils.ts +0 -207
- package/src/db/views.ts +0 -241
- package/src/docIds/conversions.ts +0 -59
- package/src/docIds/ids.ts +0 -113
- package/src/docIds/index.ts +0 -2
- package/src/docIds/newid.ts +0 -5
- package/src/docIds/params.ts +0 -174
- package/src/docUpdates/index.ts +0 -29
- package/src/environment.ts +0 -201
- package/src/errors/errors.ts +0 -119
- package/src/errors/index.ts +0 -1
- package/src/events/analytics.ts +0 -6
- package/src/events/asyncEvents/index.ts +0 -2
- package/src/events/asyncEvents/publisher.ts +0 -12
- package/src/events/asyncEvents/queue.ts +0 -22
- package/src/events/backfill.ts +0 -183
- package/src/events/documentId.ts +0 -56
- package/src/events/events.ts +0 -40
- package/src/events/identification.ts +0 -310
- package/src/events/index.ts +0 -14
- package/src/events/processors/AnalyticsProcessor.ts +0 -64
- package/src/events/processors/AuditLogsProcessor.ts +0 -93
- package/src/events/processors/LoggingProcessor.ts +0 -37
- package/src/events/processors/Processors.ts +0 -52
- package/src/events/processors/async/DocumentUpdateProcessor.ts +0 -43
- package/src/events/processors/index.ts +0 -19
- package/src/events/processors/posthog/PosthogProcessor.ts +0 -118
- package/src/events/processors/posthog/index.ts +0 -2
- package/src/events/processors/posthog/rateLimiting.ts +0 -106
- package/src/events/processors/posthog/tests/PosthogProcessor.spec.ts +0 -168
- package/src/events/processors/types.ts +0 -1
- package/src/events/publishers/account.ts +0 -35
- package/src/events/publishers/app.ts +0 -155
- package/src/events/publishers/auditLog.ts +0 -26
- package/src/events/publishers/auth.ts +0 -73
- package/src/events/publishers/automation.ts +0 -110
- package/src/events/publishers/backfill.ts +0 -74
- package/src/events/publishers/backup.ts +0 -42
- package/src/events/publishers/datasource.ts +0 -48
- package/src/events/publishers/email.ts +0 -17
- package/src/events/publishers/environmentVariable.ts +0 -38
- package/src/events/publishers/group.ts +0 -99
- package/src/events/publishers/index.ts +0 -24
- package/src/events/publishers/installation.ts +0 -38
- package/src/events/publishers/layout.ts +0 -26
- package/src/events/publishers/license.ts +0 -84
- package/src/events/publishers/org.ts +0 -37
- package/src/events/publishers/plugin.ts +0 -47
- package/src/events/publishers/query.ts +0 -88
- package/src/events/publishers/role.ts +0 -62
- package/src/events/publishers/rows.ts +0 -29
- package/src/events/publishers/screen.ts +0 -36
- package/src/events/publishers/serve.ts +0 -43
- package/src/events/publishers/table.ts +0 -70
- package/src/events/publishers/user.ts +0 -202
- package/src/events/publishers/view.ts +0 -107
- package/src/featureFlags/index.ts +0 -77
- package/src/featureFlags/tests/featureFlags.spec.ts +0 -85
- package/src/helpers.ts +0 -9
- package/src/index.ts +0 -53
- package/src/installation.ts +0 -107
- package/src/logging/alerts.ts +0 -26
- package/src/logging/correlation/correlation.ts +0 -13
- package/src/logging/correlation/index.ts +0 -1
- package/src/logging/correlation/middleware.ts +0 -17
- package/src/logging/index.ts +0 -4
- package/src/logging/pino/logger.ts +0 -232
- package/src/logging/pino/middleware.ts +0 -45
- package/src/logging/system.ts +0 -81
- package/src/logging/tests/system.spec.ts +0 -61
- package/src/middleware/adminOnly.ts +0 -9
- package/src/middleware/auditLog.ts +0 -6
- package/src/middleware/authenticated.ts +0 -193
- package/src/middleware/builderOnly.ts +0 -20
- package/src/middleware/builderOrAdmin.ts +0 -20
- package/src/middleware/csrf.ts +0 -81
- package/src/middleware/errorHandling.ts +0 -29
- package/src/middleware/index.ts +0 -21
- package/src/middleware/internalApi.ts +0 -23
- package/src/middleware/joi-validator.ts +0 -45
- package/src/middleware/matchers.ts +0 -47
- package/src/middleware/passport/datasource/google.ts +0 -95
- package/src/middleware/passport/local.ts +0 -54
- package/src/middleware/passport/sso/google.ts +0 -77
- package/src/middleware/passport/sso/oidc.ts +0 -154
- package/src/middleware/passport/sso/sso.ts +0 -165
- package/src/middleware/passport/sso/tests/google.spec.ts +0 -67
- package/src/middleware/passport/sso/tests/oidc.spec.ts +0 -152
- package/src/middleware/passport/sso/tests/sso.spec.ts +0 -197
- package/src/middleware/passport/utils.ts +0 -38
- package/src/middleware/querystringToBody.ts +0 -28
- package/src/middleware/tenancy.ts +0 -36
- package/src/middleware/tests/builder.spec.ts +0 -180
- package/src/middleware/tests/matchers.spec.ts +0 -134
- package/src/migrations/definitions.ts +0 -40
- package/src/migrations/index.ts +0 -2
- package/src/migrations/migrations.ts +0 -191
- package/src/migrations/tests/__snapshots__/migrations.spec.ts.snap +0 -11
- package/src/migrations/tests/migrations.spec.ts +0 -64
- package/src/objectStore/buckets/app.ts +0 -40
- package/src/objectStore/buckets/global.ts +0 -29
- package/src/objectStore/buckets/index.ts +0 -3
- package/src/objectStore/buckets/plugins.ts +0 -71
- package/src/objectStore/buckets/tests/app.spec.ts +0 -171
- package/src/objectStore/buckets/tests/global.spec.ts +0 -74
- package/src/objectStore/buckets/tests/plugins.spec.ts +0 -111
- package/src/objectStore/cloudfront.ts +0 -41
- package/src/objectStore/index.ts +0 -3
- package/src/objectStore/objectStore.ts +0 -440
- package/src/objectStore/utils.ts +0 -27
- package/src/platform/index.ts +0 -3
- package/src/platform/platformDb.ts +0 -6
- package/src/platform/tenants.ts +0 -101
- package/src/platform/tests/tenants.spec.ts +0 -26
- package/src/platform/users.ts +0 -90
- package/src/plugin/index.ts +0 -1
- package/src/plugin/tests/validation.spec.ts +0 -83
- package/src/plugin/utils.ts +0 -156
- package/src/queue/constants.ts +0 -6
- package/src/queue/inMemoryQueue.ts +0 -141
- package/src/queue/index.ts +0 -2
- package/src/queue/listeners.ts +0 -195
- package/src/queue/queue.ts +0 -54
- package/src/redis/index.ts +0 -6
- package/src/redis/init.ts +0 -86
- package/src/redis/redis.ts +0 -308
- package/src/redis/redlockImpl.ts +0 -139
- package/src/redis/utils.ts +0 -117
- package/src/security/encryption.ts +0 -179
- package/src/security/permissions.ts +0 -159
- package/src/security/roles.ts +0 -420
- package/src/security/sessions.ts +0 -120
- package/src/security/tests/encryption.spec.ts +0 -31
- package/src/security/tests/permissions.spec.ts +0 -145
- package/src/security/tests/sessions.spec.ts +0 -12
- package/src/tenancy/db.ts +0 -6
- package/src/tenancy/index.ts +0 -2
- package/src/tenancy/tenancy.ts +0 -140
- package/src/tenancy/tests/tenancy.spec.ts +0 -184
- package/src/timers/index.ts +0 -1
- package/src/timers/timers.ts +0 -22
- package/src/users/db.ts +0 -460
- package/src/users/events.ts +0 -176
- package/src/users/index.ts +0 -4
- package/src/users/lookup.ts +0 -102
- package/src/users/users.ts +0 -276
- package/src/users/utils.ts +0 -55
- package/src/utils/hashing.ts +0 -14
- package/src/utils/index.ts +0 -3
- package/src/utils/stringUtils.ts +0 -8
- package/src/utils/tests/utils.spec.ts +0 -191
- package/src/utils/utils.ts +0 -239
- package/tests/core/logging.ts +0 -34
- package/tests/core/utilities/index.ts +0 -6
- package/tests/core/utilities/jestUtils.ts +0 -30
- package/tests/core/utilities/mocks/alerts.ts +0 -3
- package/tests/core/utilities/mocks/date.ts +0 -2
- package/tests/core/utilities/mocks/events.ts +0 -131
- package/tests/core/utilities/mocks/fetch.ts +0 -17
- package/tests/core/utilities/mocks/index.ts +0 -10
- package/tests/core/utilities/mocks/licenses.ts +0 -107
- package/tests/core/utilities/mocks/posthog.ts +0 -7
- package/tests/core/utilities/structures/Chance.ts +0 -20
- package/tests/core/utilities/structures/accounts.ts +0 -115
- package/tests/core/utilities/structures/apps.ts +0 -21
- package/tests/core/utilities/structures/common.ts +0 -7
- package/tests/core/utilities/structures/db.ts +0 -12
- package/tests/core/utilities/structures/documents/index.ts +0 -1
- package/tests/core/utilities/structures/documents/platform/index.ts +0 -1
- package/tests/core/utilities/structures/documents/platform/installation.ts +0 -12
- package/tests/core/utilities/structures/generator.ts +0 -2
- package/tests/core/utilities/structures/index.ts +0 -15
- package/tests/core/utilities/structures/koa.ts +0 -16
- package/tests/core/utilities/structures/licenses.ts +0 -167
- package/tests/core/utilities/structures/plugins.ts +0 -19
- package/tests/core/utilities/structures/quotas.ts +0 -67
- package/tests/core/utilities/structures/scim.ts +0 -80
- package/tests/core/utilities/structures/shared.ts +0 -19
- package/tests/core/utilities/structures/sso.ts +0 -119
- package/tests/core/utilities/structures/tenants.ts +0 -5
- package/tests/core/utilities/structures/userGroups.ts +0 -10
- package/tests/core/utilities/structures/users.ts +0 -73
- package/tests/core/utilities/testContainerUtils.ts +0 -98
- package/tests/core/utilities/utils/index.ts +0 -1
- package/tests/core/utilities/utils/time.ts +0 -3
- package/tests/extra/DBTestConfiguration.ts +0 -36
- package/tests/extra/index.ts +0 -2
- package/tests/extra/testEnv.ts +0 -95
- package/tests/index.ts +0 -1
- package/tests/jestEnv.ts +0 -6
- package/tests/jestSetup.ts +0 -28
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { generateGlobalUserID } from "../../../db"
|
|
2
|
-
import { authError } from "../utils"
|
|
3
|
-
import * as users from "../../../users"
|
|
4
|
-
import * as context from "../../../context"
|
|
5
|
-
import fetch from "node-fetch"
|
|
6
|
-
import {
|
|
7
|
-
SaveSSOUserFunction,
|
|
8
|
-
SaveUserOpts,
|
|
9
|
-
SSOAuthDetails,
|
|
10
|
-
SSOUser,
|
|
11
|
-
User,
|
|
12
|
-
} from "@budibase/types"
|
|
13
|
-
|
|
14
|
-
// no-op function for user save
|
|
15
|
-
// - this allows datasource auth and access token refresh to work correctly
|
|
16
|
-
// - prefer no-op over an optional argument to ensure function is provided to login flows
|
|
17
|
-
export const ssoSaveUserNoOp: SaveSSOUserFunction = (
|
|
18
|
-
user: SSOUser,
|
|
19
|
-
opts: SaveUserOpts
|
|
20
|
-
) => Promise.resolve(user)
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Common authentication logic for third parties. e.g. OAuth, OIDC.
|
|
24
|
-
*/
|
|
25
|
-
export async function authenticate(
|
|
26
|
-
details: SSOAuthDetails,
|
|
27
|
-
requireLocalAccount: boolean = true,
|
|
28
|
-
done: any,
|
|
29
|
-
saveUserFn: SaveSSOUserFunction
|
|
30
|
-
) {
|
|
31
|
-
if (!saveUserFn) {
|
|
32
|
-
throw new Error("Save user function must be provided")
|
|
33
|
-
}
|
|
34
|
-
if (!details.userId) {
|
|
35
|
-
return authError(done, "sso user id required")
|
|
36
|
-
}
|
|
37
|
-
if (!details.email) {
|
|
38
|
-
return authError(done, "sso user email required")
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// use the third party id
|
|
42
|
-
const userId = generateGlobalUserID(details.userId)
|
|
43
|
-
|
|
44
|
-
let dbUser: User | undefined
|
|
45
|
-
|
|
46
|
-
// try to load by id
|
|
47
|
-
try {
|
|
48
|
-
dbUser = await users.getById(userId)
|
|
49
|
-
} catch (err: any) {
|
|
50
|
-
// abort when not 404 error
|
|
51
|
-
if (!err.status || err.status !== 404) {
|
|
52
|
-
return authError(
|
|
53
|
-
done,
|
|
54
|
-
"Unexpected error when retrieving existing user",
|
|
55
|
-
err
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// fallback to loading by email
|
|
61
|
-
if (!dbUser) {
|
|
62
|
-
dbUser = await users.getGlobalUserByEmail(details.email)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// exit early if there is still no user and auto creation is disabled
|
|
66
|
-
if (!dbUser && requireLocalAccount) {
|
|
67
|
-
return authError(
|
|
68
|
-
done,
|
|
69
|
-
"Email does not yet exist. You must set up your local budibase account first."
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// first time creation
|
|
74
|
-
if (!dbUser) {
|
|
75
|
-
// setup a blank user using the third party id
|
|
76
|
-
dbUser = {
|
|
77
|
-
_id: userId,
|
|
78
|
-
email: details.email,
|
|
79
|
-
roles: {},
|
|
80
|
-
tenantId: context.getTenantId(),
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
let ssoUser = await syncUser(dbUser, details)
|
|
85
|
-
// never prompt for password reset
|
|
86
|
-
ssoUser.forceResetPassword = false
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
// don't try to re-save any existing password
|
|
90
|
-
delete ssoUser.password
|
|
91
|
-
// create or sync the user
|
|
92
|
-
ssoUser = (await saveUserFn(ssoUser, {
|
|
93
|
-
hashPassword: false,
|
|
94
|
-
requirePassword: false,
|
|
95
|
-
})) as SSOUser
|
|
96
|
-
} catch (err: any) {
|
|
97
|
-
return authError(done, "Error saving user", err)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return done(null, ssoUser)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function getProfilePictureUrl(user: User, details: SSOAuthDetails) {
|
|
104
|
-
const pictureUrl = details.profile?._json.picture
|
|
105
|
-
if (pictureUrl) {
|
|
106
|
-
const response = await fetch(pictureUrl)
|
|
107
|
-
if (response.status === 200) {
|
|
108
|
-
const type = response.headers.get("content-type") as string
|
|
109
|
-
if (type.startsWith("image/")) {
|
|
110
|
-
return pictureUrl
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* @returns a user that has been sync'd with third party information
|
|
118
|
-
*/
|
|
119
|
-
async function syncUser(user: User, details: SSOAuthDetails): Promise<SSOUser> {
|
|
120
|
-
let firstName
|
|
121
|
-
let lastName
|
|
122
|
-
let pictureUrl
|
|
123
|
-
let oauth2
|
|
124
|
-
let thirdPartyProfile
|
|
125
|
-
|
|
126
|
-
if (details.profile) {
|
|
127
|
-
const profile = details.profile
|
|
128
|
-
|
|
129
|
-
if (profile.name) {
|
|
130
|
-
const name = profile.name
|
|
131
|
-
// first name
|
|
132
|
-
if (name.givenName) {
|
|
133
|
-
firstName = name.givenName
|
|
134
|
-
}
|
|
135
|
-
// last name
|
|
136
|
-
if (name.familyName) {
|
|
137
|
-
lastName = name.familyName
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
pictureUrl = await getProfilePictureUrl(user, details)
|
|
142
|
-
|
|
143
|
-
thirdPartyProfile = {
|
|
144
|
-
...profile._json,
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// oauth tokens for future use
|
|
149
|
-
if (details.oauth2) {
|
|
150
|
-
oauth2 = {
|
|
151
|
-
...details.oauth2,
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
...user,
|
|
157
|
-
provider: details.provider,
|
|
158
|
-
providerType: details.providerType,
|
|
159
|
-
firstName,
|
|
160
|
-
lastName,
|
|
161
|
-
thirdPartyProfile,
|
|
162
|
-
pictureUrl,
|
|
163
|
-
oauth2,
|
|
164
|
-
}
|
|
165
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { generator, structures } from "../../../../../tests"
|
|
2
|
-
import { SSOProviderType } from "@budibase/types"
|
|
3
|
-
|
|
4
|
-
jest.mock("passport-google-oauth")
|
|
5
|
-
const mockStrategy = require("passport-google-oauth").OAuth2Strategy
|
|
6
|
-
|
|
7
|
-
jest.mock("../sso")
|
|
8
|
-
import * as _sso from "../sso"
|
|
9
|
-
const sso = jest.mocked(_sso)
|
|
10
|
-
|
|
11
|
-
const mockSaveUserFn = jest.fn()
|
|
12
|
-
const mockDone = jest.fn()
|
|
13
|
-
|
|
14
|
-
import * as google from "../google"
|
|
15
|
-
|
|
16
|
-
describe("google", () => {
|
|
17
|
-
describe("strategyFactory", () => {
|
|
18
|
-
const googleConfig = structures.sso.googleConfig()
|
|
19
|
-
const callbackUrl = generator.url()
|
|
20
|
-
|
|
21
|
-
it("should create successfully create a google strategy", async () => {
|
|
22
|
-
await google.strategyFactory(googleConfig, callbackUrl, mockSaveUserFn)
|
|
23
|
-
|
|
24
|
-
const expectedOptions = {
|
|
25
|
-
clientID: googleConfig.clientID,
|
|
26
|
-
clientSecret: googleConfig.clientSecret,
|
|
27
|
-
callbackURL: callbackUrl,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
expect(mockStrategy).toHaveBeenCalledWith(
|
|
31
|
-
expectedOptions,
|
|
32
|
-
expect.anything()
|
|
33
|
-
)
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe("authenticate", () => {
|
|
38
|
-
const details = structures.sso.authDetails()
|
|
39
|
-
details.provider = "google"
|
|
40
|
-
details.providerType = SSOProviderType.GOOGLE
|
|
41
|
-
|
|
42
|
-
const profile = details.profile!
|
|
43
|
-
profile.provider = "google"
|
|
44
|
-
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
jest.clearAllMocks()
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it("delegates authentication to third party common", async () => {
|
|
50
|
-
const authenticate = await google.buildVerifyFn(mockSaveUserFn)
|
|
51
|
-
|
|
52
|
-
await authenticate(
|
|
53
|
-
details.oauth2.accessToken,
|
|
54
|
-
details.oauth2.refreshToken!,
|
|
55
|
-
profile,
|
|
56
|
-
mockDone
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
expect(sso.authenticate).toHaveBeenCalledWith(
|
|
60
|
-
details,
|
|
61
|
-
true,
|
|
62
|
-
mockDone,
|
|
63
|
-
mockSaveUserFn
|
|
64
|
-
)
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
})
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { generator, mocks, structures } from "../../../../../tests"
|
|
2
|
-
import {
|
|
3
|
-
JwtClaims,
|
|
4
|
-
OIDCInnerConfig,
|
|
5
|
-
SSOAuthDetails,
|
|
6
|
-
SSOProviderType,
|
|
7
|
-
} from "@budibase/types"
|
|
8
|
-
import * as _sso from "../sso"
|
|
9
|
-
import * as oidc from "../oidc"
|
|
10
|
-
|
|
11
|
-
jest.mock("@techpass/passport-openidconnect")
|
|
12
|
-
const mockStrategy = require("@techpass/passport-openidconnect").Strategy
|
|
13
|
-
|
|
14
|
-
jest.mock("../sso")
|
|
15
|
-
const sso = jest.mocked(_sso)
|
|
16
|
-
|
|
17
|
-
const mockSaveUser = jest.fn()
|
|
18
|
-
const mockDone = jest.fn()
|
|
19
|
-
|
|
20
|
-
describe("oidc", () => {
|
|
21
|
-
const callbackUrl = generator.url()
|
|
22
|
-
const oidcConfig: OIDCInnerConfig = structures.sso.oidcConfig()
|
|
23
|
-
const wellKnownConfig = structures.sso.oidcWellKnownConfig()
|
|
24
|
-
|
|
25
|
-
function mockRetrieveWellKnownConfig() {
|
|
26
|
-
// mock the request to retrieve the oidc configuration
|
|
27
|
-
mocks.fetch.mockReturnValue({
|
|
28
|
-
ok: true,
|
|
29
|
-
json: () => wellKnownConfig,
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
mockRetrieveWellKnownConfig()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe("strategyFactory", () => {
|
|
38
|
-
it("should create successfully create an oidc strategy", async () => {
|
|
39
|
-
const strategyConfiguration = await oidc.fetchStrategyConfig(
|
|
40
|
-
oidcConfig,
|
|
41
|
-
callbackUrl
|
|
42
|
-
)
|
|
43
|
-
await oidc.strategyFactory(strategyConfiguration, mockSaveUser)
|
|
44
|
-
|
|
45
|
-
expect(mocks.fetch).toHaveBeenCalledWith(oidcConfig.configUrl)
|
|
46
|
-
|
|
47
|
-
const expectedOptions = {
|
|
48
|
-
issuer: wellKnownConfig.issuer,
|
|
49
|
-
authorizationURL: wellKnownConfig.authorization_endpoint,
|
|
50
|
-
tokenURL: wellKnownConfig.token_endpoint,
|
|
51
|
-
userInfoURL: wellKnownConfig.userinfo_endpoint,
|
|
52
|
-
clientID: oidcConfig.clientID,
|
|
53
|
-
clientSecret: oidcConfig.clientSecret,
|
|
54
|
-
callbackURL: callbackUrl,
|
|
55
|
-
}
|
|
56
|
-
expect(mockStrategy).toHaveBeenCalledWith(
|
|
57
|
-
expectedOptions,
|
|
58
|
-
expect.anything()
|
|
59
|
-
)
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
describe("authenticate", () => {
|
|
64
|
-
const details: SSOAuthDetails = structures.sso.authDetails()
|
|
65
|
-
details.providerType = SSOProviderType.OIDC
|
|
66
|
-
const profile = details.profile!
|
|
67
|
-
const issuer = profile.provider
|
|
68
|
-
|
|
69
|
-
const sub = generator.string()
|
|
70
|
-
const idToken = generator.string()
|
|
71
|
-
const params = {}
|
|
72
|
-
|
|
73
|
-
let authenticateFn: any
|
|
74
|
-
let jwtClaims: JwtClaims
|
|
75
|
-
|
|
76
|
-
beforeEach(async () => {
|
|
77
|
-
jest.clearAllMocks()
|
|
78
|
-
authenticateFn = await oidc.buildVerifyFn(mockSaveUser)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
async function authenticate() {
|
|
82
|
-
await authenticateFn(
|
|
83
|
-
issuer,
|
|
84
|
-
sub,
|
|
85
|
-
profile,
|
|
86
|
-
jwtClaims,
|
|
87
|
-
details.oauth2.accessToken,
|
|
88
|
-
details.oauth2.refreshToken,
|
|
89
|
-
idToken,
|
|
90
|
-
params,
|
|
91
|
-
mockDone
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
it("passes auth details to sso module", async () => {
|
|
96
|
-
await authenticate()
|
|
97
|
-
|
|
98
|
-
expect(sso.authenticate).toHaveBeenCalledWith(
|
|
99
|
-
details,
|
|
100
|
-
false,
|
|
101
|
-
mockDone,
|
|
102
|
-
mockSaveUser
|
|
103
|
-
)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it("uses JWT email to get email", async () => {
|
|
107
|
-
delete profile._json.email
|
|
108
|
-
|
|
109
|
-
jwtClaims = {
|
|
110
|
-
email: details.email,
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
await authenticate()
|
|
114
|
-
|
|
115
|
-
expect(sso.authenticate).toHaveBeenCalledWith(
|
|
116
|
-
details,
|
|
117
|
-
false,
|
|
118
|
-
mockDone,
|
|
119
|
-
mockSaveUser
|
|
120
|
-
)
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it("uses JWT username to get email", async () => {
|
|
124
|
-
delete profile._json.email
|
|
125
|
-
|
|
126
|
-
jwtClaims = {
|
|
127
|
-
email: details.email,
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
await authenticate()
|
|
131
|
-
|
|
132
|
-
expect(sso.authenticate).toHaveBeenCalledWith(
|
|
133
|
-
details,
|
|
134
|
-
false,
|
|
135
|
-
mockDone,
|
|
136
|
-
mockSaveUser
|
|
137
|
-
)
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
it("uses JWT invalid username to get email", async () => {
|
|
141
|
-
delete profile._json.email
|
|
142
|
-
|
|
143
|
-
jwtClaims = {
|
|
144
|
-
preferred_username: "invalidUsername",
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
await expect(authenticate()).rejects.toThrow(
|
|
148
|
-
"Could not determine user email from profile"
|
|
149
|
-
)
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
})
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { structures, mocks } from "../../../../../tests"
|
|
2
|
-
import { testEnv } from "../../../../../tests/extra"
|
|
3
|
-
import { SSOAuthDetails, User } from "@budibase/types"
|
|
4
|
-
|
|
5
|
-
import { HTTPError } from "../../../../errors"
|
|
6
|
-
import * as sso from "../sso"
|
|
7
|
-
import * as context from "../../../../context"
|
|
8
|
-
|
|
9
|
-
const mockDone = jest.fn()
|
|
10
|
-
const mockSaveUser = jest.fn()
|
|
11
|
-
|
|
12
|
-
jest.mock("../../../../users")
|
|
13
|
-
import * as _users from "../../../../users"
|
|
14
|
-
const users = jest.mocked(_users)
|
|
15
|
-
|
|
16
|
-
const getErrorMessage = () => {
|
|
17
|
-
return mockDone.mock.calls[0][2].message
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("sso", () => {
|
|
21
|
-
describe("authenticate", () => {
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
jest.clearAllMocks()
|
|
24
|
-
testEnv.singleTenant()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
describe("validation", () => {
|
|
28
|
-
const testValidation = async (
|
|
29
|
-
details: SSOAuthDetails,
|
|
30
|
-
message: string
|
|
31
|
-
) => {
|
|
32
|
-
await sso.authenticate(details, false, mockDone, mockSaveUser)
|
|
33
|
-
|
|
34
|
-
expect(mockDone.mock.calls.length).toBe(1)
|
|
35
|
-
expect(getErrorMessage()).toContain(message)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
it("user id fails", async () => {
|
|
39
|
-
const details = structures.sso.authDetails()
|
|
40
|
-
details.userId = undefined!
|
|
41
|
-
|
|
42
|
-
await testValidation(details, "sso user id required")
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it("email fails", async () => {
|
|
46
|
-
const details = structures.sso.authDetails()
|
|
47
|
-
details.email = undefined!
|
|
48
|
-
|
|
49
|
-
await testValidation(details, "sso user email required")
|
|
50
|
-
})
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
function mockGetProfilePicture() {
|
|
54
|
-
mocks.fetch.mockReturnValueOnce(
|
|
55
|
-
Promise.resolve({
|
|
56
|
-
status: 200,
|
|
57
|
-
headers: { get: () => "image/" },
|
|
58
|
-
})
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
describe("when the user doesn't exist", () => {
|
|
63
|
-
let user: User
|
|
64
|
-
let details: SSOAuthDetails
|
|
65
|
-
|
|
66
|
-
beforeEach(() => {
|
|
67
|
-
users.getById.mockImplementationOnce(() => {
|
|
68
|
-
throw new HTTPError("", 404)
|
|
69
|
-
})
|
|
70
|
-
mockGetProfilePicture()
|
|
71
|
-
|
|
72
|
-
user = structures.users.user()
|
|
73
|
-
delete user._rev
|
|
74
|
-
delete user._id
|
|
75
|
-
|
|
76
|
-
details = structures.sso.authDetails(user)
|
|
77
|
-
details.userId = structures.uuid()
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
describe("when a local account is required", () => {
|
|
81
|
-
it("returns an error message", async () => {
|
|
82
|
-
const details = structures.sso.authDetails()
|
|
83
|
-
|
|
84
|
-
await sso.authenticate(details, true, mockDone, mockSaveUser)
|
|
85
|
-
|
|
86
|
-
expect(mockDone.mock.calls.length).toBe(1)
|
|
87
|
-
expect(getErrorMessage()).toContain(
|
|
88
|
-
"Email does not yet exist. You must set up your local budibase account first."
|
|
89
|
-
)
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
describe("when a local account isn't required", () => {
|
|
94
|
-
it("creates and authenticates the user", async () => {
|
|
95
|
-
const ssoUser = structures.users.ssoUser({ user, details })
|
|
96
|
-
mockSaveUser.mockReturnValueOnce(ssoUser)
|
|
97
|
-
|
|
98
|
-
await sso.authenticate(details, false, mockDone, mockSaveUser)
|
|
99
|
-
|
|
100
|
-
// default roles for new user
|
|
101
|
-
ssoUser.roles = {}
|
|
102
|
-
|
|
103
|
-
// modified external id to match user format
|
|
104
|
-
ssoUser._id = "us_" + details.userId
|
|
105
|
-
|
|
106
|
-
// new sso user won't have a password
|
|
107
|
-
delete ssoUser.password
|
|
108
|
-
|
|
109
|
-
// new user isn't saved with rev
|
|
110
|
-
delete ssoUser._rev
|
|
111
|
-
|
|
112
|
-
// tenant id added
|
|
113
|
-
ssoUser.tenantId = context.getTenantId()
|
|
114
|
-
|
|
115
|
-
expect(mockSaveUser).toBeCalledWith(ssoUser, {
|
|
116
|
-
hashPassword: false,
|
|
117
|
-
requirePassword: false,
|
|
118
|
-
})
|
|
119
|
-
expect(mockDone).toBeCalledWith(null, ssoUser)
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
describe("when the user exists", () => {
|
|
125
|
-
let existingUser: User
|
|
126
|
-
let details: SSOAuthDetails
|
|
127
|
-
|
|
128
|
-
beforeEach(() => {
|
|
129
|
-
existingUser = structures.users.user()
|
|
130
|
-
existingUser._id = structures.uuid()
|
|
131
|
-
details = structures.sso.authDetails(existingUser)
|
|
132
|
-
mockGetProfilePicture()
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
describe("exists by email", () => {
|
|
136
|
-
beforeEach(() => {
|
|
137
|
-
users.getById.mockImplementationOnce(() => {
|
|
138
|
-
throw new HTTPError("", 404)
|
|
139
|
-
})
|
|
140
|
-
users.getGlobalUserByEmail.mockReturnValueOnce(
|
|
141
|
-
Promise.resolve(existingUser)
|
|
142
|
-
)
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
it("syncs and authenticates the user", async () => {
|
|
146
|
-
const ssoUser = structures.users.ssoUser({
|
|
147
|
-
user: existingUser,
|
|
148
|
-
details,
|
|
149
|
-
})
|
|
150
|
-
mockSaveUser.mockReturnValueOnce(ssoUser)
|
|
151
|
-
|
|
152
|
-
await sso.authenticate(details, true, mockDone, mockSaveUser)
|
|
153
|
-
|
|
154
|
-
// roles preserved
|
|
155
|
-
ssoUser.roles = existingUser.roles
|
|
156
|
-
|
|
157
|
-
// existing id preserved
|
|
158
|
-
ssoUser._id = existingUser._id
|
|
159
|
-
|
|
160
|
-
expect(mockSaveUser).toBeCalledWith(ssoUser, {
|
|
161
|
-
hashPassword: false,
|
|
162
|
-
requirePassword: false,
|
|
163
|
-
})
|
|
164
|
-
expect(mockDone).toBeCalledWith(null, ssoUser)
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
describe("exists by id", () => {
|
|
169
|
-
beforeEach(() => {
|
|
170
|
-
users.getById.mockReturnValueOnce(Promise.resolve(existingUser))
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
it("syncs and authenticates the user", async () => {
|
|
174
|
-
const ssoUser = structures.users.ssoUser({
|
|
175
|
-
user: existingUser,
|
|
176
|
-
details,
|
|
177
|
-
})
|
|
178
|
-
mockSaveUser.mockReturnValueOnce(ssoUser)
|
|
179
|
-
|
|
180
|
-
await sso.authenticate(details, true, mockDone, mockSaveUser)
|
|
181
|
-
|
|
182
|
-
// roles preserved
|
|
183
|
-
ssoUser.roles = existingUser.roles
|
|
184
|
-
|
|
185
|
-
// existing id preserved
|
|
186
|
-
ssoUser._id = existingUser._id
|
|
187
|
-
|
|
188
|
-
expect(mockSaveUser).toBeCalledWith(ssoUser, {
|
|
189
|
-
hashPassword: false,
|
|
190
|
-
requirePassword: false,
|
|
191
|
-
})
|
|
192
|
-
expect(mockDone).toBeCalledWith(null, ssoUser)
|
|
193
|
-
})
|
|
194
|
-
})
|
|
195
|
-
})
|
|
196
|
-
})
|
|
197
|
-
})
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { getTenantId, isMultiTenant } from "../../context"
|
|
2
|
-
import * as configs from "../../configs"
|
|
3
|
-
import { ConfigType, GoogleInnerConfig } from "@budibase/types"
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Utility to handle authentication errors.
|
|
7
|
-
*
|
|
8
|
-
* @param {*} done The passport callback.
|
|
9
|
-
* @param {*} message Message that will be returned in the response body
|
|
10
|
-
* @param {*} err (Optional) error that will be logged
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export function authError(done: Function, message: string, err?: any) {
|
|
14
|
-
return done(
|
|
15
|
-
err,
|
|
16
|
-
null, // never return a user
|
|
17
|
-
{ message: message }
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function ssoCallbackUrl(
|
|
22
|
-
type: ConfigType,
|
|
23
|
-
config?: GoogleInnerConfig
|
|
24
|
-
) {
|
|
25
|
-
// incase there is a callback URL from before
|
|
26
|
-
if (config && (config as GoogleInnerConfig).callbackURL) {
|
|
27
|
-
return (config as GoogleInnerConfig).callbackURL as string
|
|
28
|
-
}
|
|
29
|
-
const settingsConfig = await configs.getSettingsConfig()
|
|
30
|
-
|
|
31
|
-
let callbackUrl = `/api/global/auth`
|
|
32
|
-
if (isMultiTenant()) {
|
|
33
|
-
callbackUrl += `/${getTenantId()}`
|
|
34
|
-
}
|
|
35
|
-
callbackUrl += `/${type}/callback`
|
|
36
|
-
|
|
37
|
-
return `${settingsConfig.platformUrl}${callbackUrl}`
|
|
38
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Ctx } from "@budibase/types"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Expects a standard "query" query string property which is the JSON body
|
|
5
|
-
* of the request, which has to be sent via query string due to the requirement
|
|
6
|
-
* of making an endpoint a GET request e.g. downloading a file stream.
|
|
7
|
-
*/
|
|
8
|
-
export default function (ctx: Ctx, next: any) {
|
|
9
|
-
const queryString = ctx.request.query?.query as string | undefined
|
|
10
|
-
if (ctx.request.method.toLowerCase() !== "get") {
|
|
11
|
-
ctx.throw(
|
|
12
|
-
500,
|
|
13
|
-
"Query to download middleware can only be used for get requests."
|
|
14
|
-
)
|
|
15
|
-
}
|
|
16
|
-
if (!queryString) {
|
|
17
|
-
return next()
|
|
18
|
-
}
|
|
19
|
-
const decoded = decodeURIComponent(queryString)
|
|
20
|
-
let json
|
|
21
|
-
try {
|
|
22
|
-
json = JSON.parse(decoded)
|
|
23
|
-
} catch (err) {
|
|
24
|
-
return next()
|
|
25
|
-
}
|
|
26
|
-
ctx.request.body = json
|
|
27
|
-
return next()
|
|
28
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { doInTenant } from "../context"
|
|
2
|
-
import { getTenantIDFromCtx } from "../tenancy"
|
|
3
|
-
import { buildMatcherRegex, matches } from "./matchers"
|
|
4
|
-
import { Header } from "../constants"
|
|
5
|
-
import {
|
|
6
|
-
BBContext,
|
|
7
|
-
EndpointMatcher,
|
|
8
|
-
GetTenantIdOptions,
|
|
9
|
-
TenantResolutionStrategy,
|
|
10
|
-
} from "@budibase/types"
|
|
11
|
-
|
|
12
|
-
export default function (
|
|
13
|
-
allowQueryStringPatterns: EndpointMatcher[],
|
|
14
|
-
noTenancyPatterns: EndpointMatcher[],
|
|
15
|
-
opts: { noTenancyRequired?: boolean } = { noTenancyRequired: false }
|
|
16
|
-
) {
|
|
17
|
-
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
|
18
|
-
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
|
19
|
-
|
|
20
|
-
return async function (ctx: BBContext | any, next: any) {
|
|
21
|
-
const allowNoTenant =
|
|
22
|
-
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
|
|
23
|
-
const tenantOpts: GetTenantIdOptions = {
|
|
24
|
-
allowNoTenant,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const allowQs = !!matches(ctx, allowQsOptions)
|
|
28
|
-
if (!allowQs) {
|
|
29
|
-
tenantOpts.excludeStrategies = [TenantResolutionStrategy.QUERY]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const tenantId = getTenantIDFromCtx(ctx, tenantOpts)
|
|
33
|
-
ctx.set(Header.TENANT_ID, tenantId as string)
|
|
34
|
-
return doInTenant(tenantId, next)
|
|
35
|
-
}
|
|
36
|
-
}
|