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.
* @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)
return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}`
@ -107,7 +107,7 @@ exports.generateConfigID = ({ type, group, user }) => {
/**
* 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)
return {
@ -116,3 +116,48 @@ exports.getConfigParams = ({ type, group, user }, otherProps = {}) => {
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,
generateConfigID,
getConfigParams,
determineScopedConfig,
} = require("./db/utils")
// Strategies
@ -71,6 +72,7 @@ module.exports = {
getEmailFromUserID,
generateConfigID,
getConfigParams,
determineScopedConfig,
hash,
compare,
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.
* @returns Dynamically configured Passport Google Strategy
*/
exports.strategyFactory = async function(scope) {
exports.strategyFactory = async function(config) {
try {
const db = database.getDB(StaticDatabases.GLOBAL.name)
const config = await db.get(scope)
const { clientID, clientSecret, callbackURL } = config
if (!clientID || !clientSecret || !callbackURL) {

View File

@ -1,5 +1,5 @@
const CouchDB = require("../../../db")
const { StaticDatabases } = require("@budibase/auth")
const { StaticDatabases, determineScopedConfig } = require("@budibase/auth")
const { generateConfigID, getConfigParams } = require("@budibase/auth")
const GLOBAL_DB = StaticDatabases.GLOBAL.name
@ -59,49 +59,11 @@ exports.find = async function(ctx) {
}
try {
const response = await db.allDocs(
getConfigParams(
{
// Find the config with the most granular scope based on context
const scopedConfig = await determineScopedConfig(db, {
type: ctx.params.type,
user: userId,
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) {

View File

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

View File

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