scoped configuration management

This commit is contained in:
Martin McKeaveney 2021-04-22 13:46:54 +01:00
parent caa4f0dfb7
commit 9a12239e62
6 changed files with 67 additions and 55 deletions

View File

@ -98,7 +98,7 @@ exports.getTemplateParams = (ownerId, templateId, otherProps = {}) => {
* Generates a new configuration ID. * Generates a new configuration ID.
* @returns {string} The new configuration ID which the config doc can be stored under. * @returns {string} The new configuration ID which the config doc can be stored under.
*/ */
exports.generateConfigID = ({ type, group, user }) => { const generateConfigID = ({ type, group, user }) => {
const scope = [type, group, user].filter(Boolean).join(SEPARATOR) const scope = [type, group, user].filter(Boolean).join(SEPARATOR)
return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}` return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}`
@ -107,7 +107,7 @@ exports.generateConfigID = ({ type, group, user }) => {
/** /**
* Gets parameters for retrieving configurations. * Gets parameters for retrieving configurations.
*/ */
exports.getConfigParams = ({ type, group, user }, otherProps = {}) => { const getConfigParams = ({ type, group, user }, otherProps = {}) => {
const scope = [type, group, user].filter(Boolean).join(SEPARATOR) const scope = [type, group, user].filter(Boolean).join(SEPARATOR)
return { return {
@ -116,3 +116,48 @@ exports.getConfigParams = ({ type, group, user }, otherProps = {}) => {
endkey: `${DocumentTypes.CONFIG}${SEPARATOR}${scope}${UNICODE_MAX}`, endkey: `${DocumentTypes.CONFIG}${SEPARATOR}${scope}${UNICODE_MAX}`,
} }
} }
/**
* Returns the most granular configuration document from the DB based on the type, group and userID passed.
* @param {*} db - db instance to quer
* @param {Object} scopes - the type, group and userID scopes of the configuration.
* @returns The most granular configuration document based on the scope.
*/
const determineScopedConfig = async function(db, { type, user, group }) {
const response = await db.allDocs(
getConfigParams(
{ type, user, group },
{
include_docs: true,
}
)
)
const configs = response.rows.map(row => row.doc)
// Find the config with the most granular scope based on context
const scopedConfig = configs.find(config => {
// Config is specific to a user and a group
if (config._id.includes(generateConfigID({ type, user, group }))) {
return config
}
// Config is specific to a user
if (config._id.includes(generateConfigID({ type, user }))) {
return config
}
// Config is specific to a group only
if (config._id.includes(generateConfigID({ type, group }))) {
return config
}
// Config specific to a config type only
return config
})
return scopedConfig
}
exports.generateConfigID = generateConfigID
exports.getConfigParams = getConfigParams
exports.determineScopedConfig = determineScopedConfig

View File

@ -22,6 +22,7 @@ const {
getEmailFromUserID, getEmailFromUserID,
generateConfigID, generateConfigID,
getConfigParams, getConfigParams,
determineScopedConfig,
} = require("./db/utils") } = require("./db/utils")
// Strategies // Strategies
@ -71,6 +72,7 @@ module.exports = {
getEmailFromUserID, getEmailFromUserID,
generateConfigID, generateConfigID,
getConfigParams, getConfigParams,
determineScopedConfig,
hash, hash,
compare, compare,
getAppId, getAppId,

View File

@ -51,12 +51,8 @@ async function authenticate(token, tokenSecret, profile, done) {
* 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 Google Strategy * @returns Dynamically configured Passport Google Strategy
*/ */
exports.strategyFactory = async function(scope) { exports.strategyFactory = async function(config) {
try { try {
const db = database.getDB(StaticDatabases.GLOBAL.name)
const config = await db.get(scope)
const { clientID, clientSecret, callbackURL } = config const { clientID, clientSecret, callbackURL } = config
if (!clientID || !clientSecret || !callbackURL) { if (!clientID || !clientSecret || !callbackURL) {

View File

@ -1,5 +1,5 @@
const CouchDB = require("../../../db") const CouchDB = require("../../../db")
const { StaticDatabases } = require("@budibase/auth") const { StaticDatabases, determineScopedConfig } = require("@budibase/auth")
const { generateConfigID, getConfigParams } = require("@budibase/auth") const { generateConfigID, getConfigParams } = require("@budibase/auth")
const GLOBAL_DB = StaticDatabases.GLOBAL.name const GLOBAL_DB = StaticDatabases.GLOBAL.name
@ -59,49 +59,11 @@ exports.find = async function(ctx) {
} }
try { try {
const response = await db.allDocs( // Find the config with the most granular scope based on context
getConfigParams( const scopedConfig = await determineScopedConfig(db, {
{
type: ctx.params.type, type: ctx.params.type,
user: userId, user: userId,
group, group,
},
{
include_docs: true,
}
)
)
const configs = response.rows.map(row => row.doc)
// Find the config with the most granular scope based on context
const scopedConfig = configs.find(config => {
// Config is specific to a user and a group
if (
config._id.includes(
generateConfigID({ type: ctx.params.type, user: userId, group })
)
) {
return config
}
// Config is specific to a user
if (
config._id.includes(
generateConfigID({ type: ctx.params.type, user: userId })
)
) {
return config
}
// Config is specific to a group only
if (
config._id.includes(generateConfigID({ type: ctx.params.type, group }))
) {
return config
}
// Config specific to a config type only
return config
}) })
if (scopedConfig) { if (scopedConfig) {

View File

@ -74,6 +74,6 @@ exports.find = async ctx => {
exports.destroy = async ctx => { exports.destroy = async ctx => {
// TODO // TODO
const db = new CouchDB(GLOBAL_DB) // const db = new CouchDB(GLOBAL_DB)
ctx.body = {} ctx.body = {}
} }

View File

@ -1,10 +1,14 @@
const { determineScopedConfig } = require("@budibase/auth")
const authPkg = require("@budibase/auth") const authPkg = require("@budibase/auth")
const { google } = require("@budibase/auth/src/middleware") const { google } = require("@budibase/auth/src/middleware")
const { Configs } = require("../../constants") const { Configs } = require("../../constants")
const CouchDB = require("../../db")
const { clearCookie } = authPkg.utils const { clearCookie } = authPkg.utils
const { Cookies } = authPkg const { Cookies } = authPkg
const { passport } = authPkg.auth const { passport } = authPkg.auth
const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name
exports.authenticate = async (ctx, next) => { exports.authenticate = async (ctx, next) => {
return passport.authenticate("local", async (err, user) => { return passport.authenticate("local", async (err, user) => {
if (err) { if (err) {
@ -41,11 +45,12 @@ exports.logout = async ctx => {
* On a successful login, you will be redirected to the googleAuth callback route. * On a successful login, you will be redirected to the googleAuth callback route.
*/ */
exports.googlePreAuth = async (ctx, next) => { exports.googlePreAuth = async (ctx, next) => {
const strategy = await google.strategyFactory({ const db = new CouchDB(GLOBAL_DB)
const config = await determineScopedConfig(db, {
type: Configs.GOOGLE, type: Configs.GOOGLE,
user: ctx.user._id,
group: ctx.query.group, group: ctx.query.group,
}) })
const strategy = await google.strategyFactory(config)
return passport.authenticate(strategy, { return passport.authenticate(strategy, {
scope: ["profile", "email"], scope: ["profile", "email"],
@ -53,11 +58,13 @@ exports.googlePreAuth = async (ctx, next) => {
} }
exports.googleAuth = async (ctx, next) => { exports.googleAuth = async (ctx, next) => {
const strategy = await google.strategyFactory({ const db = new CouchDB(GLOBAL_DB)
const config = await determineScopedConfig(db, {
type: Configs.GOOGLE, type: Configs.GOOGLE,
user: ctx.user._id,
group: ctx.query.group, group: ctx.query.group,
}) })
const strategy = await google.strategyFactory(config)
return passport.authenticate( return passport.authenticate(
strategy, strategy,