1
0
Fork 0
mirror of synced 2024-05-02 11:32:26 +12:00

Merge commit

This commit is contained in:
Dean 2022-06-23 14:29:19 +01:00
parent 1b73961240
commit dc20ecc5ff
11 changed files with 211 additions and 26 deletions

14
.vscode/settings.json vendored
View file

@ -3,5 +3,17 @@
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll": true "source.fixAll": true
}, },
"editor.defaultFormatter": "svelte.svelte-vscode" "editor.defaultFormatter": "svelte.svelte-vscode",
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"debug.javascript.terminalOptions": {
"skipFiles": [
"${workspaceFolder}/packages/backend-core/node_modules/**",
"<node_internals>/**"
]
},
} }

View file

@ -36,6 +36,7 @@
"passport-google-oauth": "2.0.0", "passport-google-oauth": "2.0.0",
"passport-jwt": "4.0.0", "passport-jwt": "4.0.0",
"passport-local": "1.0.0", "passport-local": "1.0.0",
"passport-oauth2-refresh": "^2.1.0",
"posthog-node": "1.3.0", "posthog-node": "1.3.0",
"pouchdb": "7.3.0", "pouchdb": "7.3.0",
"pouchdb-find": "7.2.2", "pouchdb-find": "7.2.2",

View file

@ -2,6 +2,9 @@ const passport = require("koa-passport")
const LocalStrategy = require("passport-local").Strategy const LocalStrategy = require("passport-local").Strategy
const JwtStrategy = require("passport-jwt").Strategy const JwtStrategy = require("passport-jwt").Strategy
const { getGlobalDB } = require("./tenancy") const { getGlobalDB } = require("./tenancy")
const refresh = require("passport-oauth2-refresh")
const { Configs } = require("./constants")
const { getScopedConfig } = require("./db/utils")
const { const {
jwt, jwt,
local, local,
@ -34,6 +37,55 @@ passport.deserializeUser(async (user, done) => {
} }
}) })
//requestAccessStrategy
//refreshOAuthAccessToken
//configId for google and OIDC??
async function reUpToken(refreshToken, configId) {
const db = getGlobalDB()
console.log(refreshToken, configId)
const config = await getScopedConfig(db, {
type: Configs.OIDC,
group: {}, //ctx.query.group, this was an empty object when authentication initially
})
const chosenConfig = config.configs[0] //.filter((c) => c.uuid === configId)[0]
let callbackUrl = await oidc.oidcCallbackUrl(db, chosenConfig)
//Remote Config
const enrichedConfig = await oidc.fetchOIDCStrategyConfig(
chosenConfig,
callbackUrl
)
const strategy = await oidc.strategyFactory(enrichedConfig, () => {
console.log("saveFn RETURN ARGS", JSON.stringify(arguments))
})
try {
refresh.use(strategy, {
setRefreshOAuth2() {
return strategy._getOAuth2Client(enrichedConfig)
},
})
console.log("Testing")
// By default, the strat calls itself "openidconnect"
// refresh.requestNewAccessToken(
// 'openidconnect',
// refToken,
// (err, accessToken, refreshToken) => {
// console.log("REAUTH CB", err, accessToken, refreshToken);
// })
} catch (err) {
console.error(err)
throw new Error("Error constructing OIDC refresh strategy", err)
}
console.log("end")
}
module.exports = { module.exports = {
buildAuthMiddleware: authenticated, buildAuthMiddleware: authenticated,
passport, passport,
@ -46,4 +98,5 @@ module.exports = {
authError, authError,
buildCsrfMiddleware: csrf, buildCsrfMiddleware: csrf,
internalApi, internalApi,
reUpToken,
} }

View file

@ -384,7 +384,10 @@ export const getScopedFullConfig = async function (
if (type === Configs.SETTINGS) { if (type === Configs.SETTINGS) {
if (scopedConfig && scopedConfig.doc) { if (scopedConfig && scopedConfig.doc) {
// overrides affected by environment variables // overrides affected by environment variables
scopedConfig.doc.config.platformUrl = await getPlatformUrl() scopedConfig.doc.config.platformUrl = await getPlatformUrl(
{ tenantAware: true },
db
)
scopedConfig.doc.config.analyticsEnabled = scopedConfig.doc.config.analyticsEnabled =
await events.analytics.enabled() await events.analytics.enabled()
} else { } else {
@ -393,7 +396,7 @@ export const getScopedFullConfig = async function (
doc: { doc: {
_id: generateConfigID({ type, user, workspace }), _id: generateConfigID({ type, user, workspace }),
config: { config: {
platformUrl: await getPlatformUrl(), platformUrl: await getPlatformUrl({ tenantAware: true }, db),
analyticsEnabled: await events.analytics.enabled(), analyticsEnabled: await events.analytics.enabled(),
}, },
}, },
@ -404,7 +407,10 @@ export const getScopedFullConfig = async function (
return scopedConfig && scopedConfig.doc return scopedConfig && scopedConfig.doc
} }
export const getPlatformUrl = async (opts = { tenantAware: true }) => { export const getPlatformUrl = async (
opts = { tenantAware: true },
db = null
) => {
let platformUrl = env.PLATFORM_URL || "http://localhost:10000" let platformUrl = env.PLATFORM_URL || "http://localhost:10000"
if (!env.SELF_HOSTED && env.MULTI_TENANCY && opts.tenantAware) { if (!env.SELF_HOSTED && env.MULTI_TENANCY && opts.tenantAware) {
@ -414,11 +420,11 @@ export const getPlatformUrl = async (opts = { tenantAware: true }) => {
platformUrl = platformUrl.replace("://", `://${tenantId}.`) platformUrl = platformUrl.replace("://", `://${tenantId}.`)
} }
} else if (env.SELF_HOSTED) { } else if (env.SELF_HOSTED) {
const db = getGlobalDB() const dbx = db ? db : getGlobalDB()
// get the doc directly instead of with getScopedConfig to prevent loop // get the doc directly instead of with getScopedConfig to prevent loop
let settings let settings
try { try {
settings = await db.get(generateConfigID({ type: Configs.SETTINGS })) settings = await dbx.get(generateConfigID({ type: Configs.SETTINGS }))
} catch (e: any) { } catch (e: any) {
if (e.status !== 404) { if (e.status !== 404) {
throw e throw e

View file

@ -1,6 +1,7 @@
const fetch = require("node-fetch") const fetch = require("node-fetch")
const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy
const { authenticateThirdParty } = require("./third-party-common") const { authenticateThirdParty } = require("./third-party-common")
const { ssoCallbackUrl } = require("./utils")
const buildVerifyFn = saveUserFn => { const buildVerifyFn = saveUserFn => {
/** /**
@ -89,11 +90,22 @@ function validEmail(value) {
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport. * from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
* @returns Dynamically configured Passport OIDC Strategy * @returns Dynamically configured Passport OIDC Strategy
*/ */
exports.strategyFactory = async function (config, callbackUrl, saveUserFn) { exports.strategyFactory = async function (config, saveUserFn) {
try {
const verify = buildVerifyFn(saveUserFn)
return new OIDCStrategy(config, verify)
} catch (err) {
console.error(err)
throw new Error("Error constructing OIDC authentication strategy", err)
}
}
export const fetchOIDCStrategyConfig = async (config, callbackUrl) => {
try { try {
const { clientID, clientSecret, configUrl } = config const { clientID, clientSecret, configUrl } = config
if (!clientID || !clientSecret || !callbackUrl || !configUrl) { if (!clientID || !clientSecret || !callbackUrl || !configUrl) {
//check for remote config and all required elements
throw new Error( throw new Error(
"Configuration invalid. Must contain clientID, clientSecret, callbackUrl and configUrl" "Configuration invalid. Must contain clientID, clientSecret, callbackUrl and configUrl"
) )
@ -109,24 +121,24 @@ exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
const body = await response.json() const body = await response.json()
const verify = buildVerifyFn(saveUserFn) return {
return new OIDCStrategy( issuer: body.issuer,
{ authorizationURL: body.authorization_endpoint,
issuer: body.issuer, tokenURL: body.token_endpoint,
authorizationURL: body.authorization_endpoint, userInfoURL: body.userinfo_endpoint,
tokenURL: body.token_endpoint, clientID: clientID,
userInfoURL: body.userinfo_endpoint, clientSecret: clientSecret,
clientID: clientID, callbackURL: callbackUrl,
clientSecret: clientSecret, }
callbackURL: callbackUrl,
},
verify
)
} catch (err) { } catch (err) {
console.error(err) console.error(err)
throw new Error("Error constructing OIDC authentication strategy", err) throw new Error("Error constructing OIDC authentication configuration", err)
} }
} }
export const oidcCallbackUrl = async (db, config) => {
return ssoCallbackUrl(db, config, "oidc")
}
// expose for testing // expose for testing
exports.buildVerifyFn = buildVerifyFn exports.buildVerifyFn = buildVerifyFn

View file

@ -1,3 +1,7 @@
const { getGlobalDB, isMultiTenant, getTenantId } = require("../../tenancy")
const { getScopedConfig } = require("../../db/utils")
const { Configs } = require("../../constants")
/** /**
* Utility to handle authentication errors. * Utility to handle authentication errors.
* *
@ -5,6 +9,7 @@
* @param {*} message Message that will be returned in the response body * @param {*} message Message that will be returned in the response body
* @param {*} err (Optional) error that will be logged * @param {*} err (Optional) error that will be logged
*/ */
exports.authError = function (done, message, err = null) { exports.authError = function (done, message, err = null) {
return done( return done(
err, err,
@ -12,3 +17,23 @@ exports.authError = function (done, message, err = null) {
{ message: message } { message: message }
) )
} }
exports.ssoCallbackUrl = async (db, config, type) => {
// incase there is a callback URL from before
if (config && config.callbackURL) {
return config.callbackURL
}
const dbx = db ? db : getGlobalDB()
const publicConfig = await getScopedConfig(dbx, {
type: Configs.SETTINGS,
})
let callbackUrl = `/api/global/auth`
if (isMultiTenant()) {
callbackUrl += `/${getTenantId()}`
}
callbackUrl += `/${type}/callback`
return `${publicConfig.platformUrl}${callbackUrl}`
}

View file

@ -291,6 +291,11 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/types@^1.0.206":
version "1.0.208"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.0.208.tgz#c45cb494fb5b85229e15a34c6ac1805bae5be867"
integrity sha512-zKIHg6TGK+soVxMNZNrGypP3DCrd3jhlUQEFeQ+rZR6/tCue1G74bjzydY5FjnLEsXeLH1a0hkS5HulTmvQ2bA==
"@istanbuljs/load-nyc-config@^1.0.0": "@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@ -3965,6 +3970,11 @@ passport-oauth1@1.x.x:
passport-strategy "1.x.x" passport-strategy "1.x.x"
utils-merge "1.x.x" utils-merge "1.x.x"
passport-oauth2-refresh@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/passport-oauth2-refresh/-/passport-oauth2-refresh-2.1.0.tgz#c31cd133826383f5539d16ad8ab4f35ca73ce4a4"
integrity sha512-4ML7ooCESCqiTgdDBzNUFTBcPR8zQq9iM6eppEUGMMvLdsjqRL93jKwWm4Az3OJcI+Q2eIVyI8sVRcPFvxcF/A==
passport-oauth2@1.x.x: passport-oauth2@1.x.x:
version "1.6.1" version "1.6.1"
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.1.tgz#c5aee8f849ce8bd436c7f81d904a3cd1666f181b" resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.1.tgz#c5aee8f849ce8bd436c7f81d904a3cd1666f181b"

View file

@ -49,6 +49,43 @@ export const getBindableProperties = (asset, componentId) => {
] ]
} }
/**
* Gets all rest bindable data fields
*/
export const getRestBindings = () => {
const userBindings = getUserBindings()
const oauthBindings = getAuthBindings()
return [...userBindings, ...oauthBindings]
}
/**
* Utility - coverting a map of readable bindings to runtime
*/
export const readableToRuntimeMap = (bindings, ctx) => {
if (!bindings || !ctx) {
return {}
}
return Object.keys(ctx).reduce((acc, key) => {
let parsedQuery = readableToRuntimeBinding(bindings, ctx[key])
acc[key] = parsedQuery
return acc
}, {})
}
/**
* Utility - coverting a map of runtime bindings to readable
*/
export const runtimeToReadableMap = (bindings, ctx) => {
if (!bindings || !ctx) {
return {}
}
return Object.keys(ctx).reduce((acc, key) => {
let parsedQuery = runtimeToReadableBinding(bindings, ctx[key])
acc[key] = parsedQuery
return acc
}, {})
}
/** /**
* Gets the bindable properties exposed by a certain component. * Gets the bindable properties exposed by a certain component.
*/ */
@ -298,10 +335,22 @@ const getUserBindings = () => {
providerId: "user", providerId: "user",
}) })
}) })
return bindings return bindings
} }
const getAuthBindings = () => {
return [
{
type: "context",
runtimeBinding: `${makePropSafe("user")}.${makePropSafe(
"oauth2"
)}.${makePropSafe("accessToken")}`,
readableBinding: "OAuthToken",
providerId: "user",
},
]
}
/** /**
* Gets all device bindings that are globally available. * Gets all device bindings that are globally available.
*/ */

View file

@ -8,6 +8,8 @@ import { QUERY_THREAD_TIMEOUT } from "../../../environment"
import { getAppDB } from "@budibase/backend-core/context" import { getAppDB } from "@budibase/backend-core/context"
import { quotas } from "@budibase/pro" import { quotas } from "@budibase/pro"
import { events } from "@budibase/backend-core" import { events } from "@budibase/backend-core"
import { getCookie } from "@budibase/backend-core/utils"
import { Cookies } from "@budibase/backend-core/constants"
const Runner = new Thread(ThreadType.QUERY, { const Runner = new Thread(ThreadType.QUERY, {
timeoutMs: QUERY_THREAD_TIMEOUT || 10000, timeoutMs: QUERY_THREAD_TIMEOUT || 10000,
@ -119,6 +121,10 @@ export async function preview(ctx: any) {
// this stops dynamic variables from calling the same query // this stops dynamic variables from calling the same query
const { fields, parameters, queryVerb, transformer, queryId } = query const { fields, parameters, queryVerb, transformer, queryId } = query
//check for oAuth elements here?
const configId = getCookie(ctx, Cookies.OIDC_CONFIG)
console.log(configId)
try { try {
const runFn = () => const runFn = () =>
Runner.run({ Runner.run({
@ -130,7 +136,6 @@ export async function preview(ctx: any) {
transformer, transformer,
queryId, queryId,
}) })
const { rows, keys, info, extra } = await quotas.addQuery(runFn) const { rows, keys, info, extra } = await quotas.addQuery(runFn)
await events.query.previewed(datasource, query) await events.query.previewed(datasource, query)
ctx.body = { ctx.body = {

View file

@ -4,6 +4,7 @@ const ScriptRunner = require("../utilities/scriptRunner")
const { integrations } = require("../integrations") const { integrations } = require("../integrations")
const { processStringSync } = require("@budibase/string-templates") const { processStringSync } = require("@budibase/string-templates")
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context") const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
// const { reUpToken } = require("@budibase/backend-core/auth")
const { isSQL } = require("../integrations/utils") const { isSQL } = require("../integrations/utils")
const { const {
enrichQueryFields, enrichQueryFields,
@ -30,6 +31,11 @@ class QueryRunner {
async execute() { async execute() {
let { datasource, fields, queryVerb, transformer } = this let { datasource, fields, queryVerb, transformer } = this
// if(this.ctx.user.oauth2?.refreshToken){
// reUpToken(this.ctx.user.oauth2?.refreshToken)
// }
const Integration = integrations[datasource.source] const Integration = integrations[datasource.source]
if (!Integration) { if (!Integration) {
throw "Integration type does not exist." throw "Integration type does not exist."
@ -79,6 +85,7 @@ class QueryRunner {
this.cachedVariables.length > 0 && this.cachedVariables.length > 0 &&
!this.hasRerun !this.hasRerun
) { ) {
// return { info }
this.hasRerun = true this.hasRerun = true
// invalidate the cache value // invalidate the cache value
await threadUtils.invalidateDynamicVariables(this.cachedVariables) await threadUtils.invalidateDynamicVariables(this.cachedVariables)

View file

@ -224,7 +224,7 @@ export const googleAuth = async (ctx: any, next: any) => {
)(ctx, next) )(ctx, next)
} }
async function oidcStrategyFactory(ctx: any, configId: any) { export const oidcStrategyFactory = async (ctx: any, configId: any) => {
const db = getGlobalDB() const db = getGlobalDB()
const config = await core.db.getScopedConfig(db, { const config = await core.db.getScopedConfig(db, {
type: Configs.OIDC, type: Configs.OIDC,
@ -234,7 +234,12 @@ async function oidcStrategyFactory(ctx: any, configId: any) {
const chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0] const chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
let callbackUrl = await exports.oidcCallbackUrl(chosenConfig) let callbackUrl = await exports.oidcCallbackUrl(chosenConfig)
return oidc.strategyFactory(chosenConfig, callbackUrl, users.save) //Remote Config
const enrichedConfig = await oidc.fetchOIDCStrategyConfig(
chosenConfig,
callbackUrl
)
return oidc.strategyFactory(enrichedConfig, users.save)
} }
/** /**
@ -249,7 +254,7 @@ export const oidcPreAuth = async (ctx: any, next: any) => {
return passport.authenticate(strategy, { return passport.authenticate(strategy, {
// required 'openid' scope is added by oidc strategy factory // required 'openid' scope is added by oidc strategy factory
scope: ["profile", "email"], scope: ["profile", "email", "offline_access"], //auth0 offline_access scope required for the refresh token behaviour.
})(ctx, next) })(ctx, next)
} }