Merge pull request #1401 from Budibase/configuration-management
Configuration management and Google Authentication
This commit is contained in:
commit
f18a5ee667
|
@ -14,6 +14,7 @@ const DocumentTypes = {
|
||||||
USER: "us",
|
USER: "us",
|
||||||
APP: "app",
|
APP: "app",
|
||||||
GROUP: "group",
|
GROUP: "group",
|
||||||
|
CONFIG: "config",
|
||||||
TEMPLATE: "template",
|
TEMPLATE: "template",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,8 +48,8 @@ exports.getGroupParams = (id = "", otherProps = {}) => {
|
||||||
* Generates a new global user ID.
|
* Generates a new global user ID.
|
||||||
* @returns {string} The new user ID which the user doc can be stored under.
|
* @returns {string} The new user ID which the user doc can be stored under.
|
||||||
*/
|
*/
|
||||||
exports.generateGlobalUserID = () => {
|
exports.generateGlobalUserID = id => {
|
||||||
return `${DocumentTypes.USER}${SEPARATOR}${newid()}`
|
return `${DocumentTypes.USER}${SEPARATOR}${id || newid()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,3 +93,70 @@ exports.getTemplateParams = (ownerId, templateId, otherProps = {}) => {
|
||||||
endkey: `${final}${UNICODE_MAX}`,
|
endkey: `${final}${UNICODE_MAX}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new configuration ID.
|
||||||
|
* @returns {string} The new configuration ID which the config doc can be stored under.
|
||||||
|
*/
|
||||||
|
const generateConfigID = ({ type, group, user }) => {
|
||||||
|
const scope = [type, group, user].filter(Boolean).join(SEPARATOR)
|
||||||
|
|
||||||
|
return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets parameters for retrieving configurations.
|
||||||
|
*/
|
||||||
|
const getConfigParams = ({ type, group, user }, otherProps = {}) => {
|
||||||
|
const scope = [type, group, user].filter(Boolean).join(SEPARATOR)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...otherProps,
|
||||||
|
startkey: `${DocumentTypes.CONFIG}${SEPARATOR}${scope}`,
|
||||||
|
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 {Object} db - db instance to query
|
||||||
|
* @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 => {
|
||||||
|
const config = row.doc
|
||||||
|
|
||||||
|
// Config is specific to a user and a group
|
||||||
|
if (config._id.includes(generateConfigID({ type, user, group }))) {
|
||||||
|
config.score = 4
|
||||||
|
} else if (config._id.includes(generateConfigID({ type, user }))) {
|
||||||
|
// Config is specific to a user only
|
||||||
|
config.score = 3
|
||||||
|
} else if (config._id.includes(generateConfigID({ type, group }))) {
|
||||||
|
// Config is specific to a group only
|
||||||
|
config.score = 2
|
||||||
|
} else if (config._id.includes(generateConfigID({ type }))) {
|
||||||
|
// Config is specific to a type only
|
||||||
|
config.score = 1
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
// Find the config with the most granular scope based on context
|
||||||
|
const scopedConfig = configs.sort((a, b) => b.score - a.score)[0]
|
||||||
|
|
||||||
|
return scopedConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.generateConfigID = generateConfigID
|
||||||
|
exports.getConfigParams = getConfigParams
|
||||||
|
exports.determineScopedConfig = determineScopedConfig
|
||||||
|
|
|
@ -2,7 +2,4 @@ module.exports = {
|
||||||
JWT_SECRET: process.env.JWT_SECRET,
|
JWT_SECRET: process.env.JWT_SECRET,
|
||||||
COUCH_DB_URL: process.env.COUCH_DB_URL,
|
COUCH_DB_URL: process.env.COUCH_DB_URL,
|
||||||
SALT_ROUNDS: process.env.SALT_ROUNDS,
|
SALT_ROUNDS: process.env.SALT_ROUNDS,
|
||||||
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
|
||||||
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
|
|
||||||
GOOGLE_AUTH_CALLBACK_URL: process.env.GOOGLE_AUTH_CALLBACK_URL,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
const passport = require("koa-passport")
|
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 GoogleStrategy = require("passport-google-oauth").Strategy
|
|
||||||
const { setDB, getDB } = require("./db")
|
|
||||||
const { StaticDatabases } = require("./db/utils")
|
const { StaticDatabases } = require("./db/utils")
|
||||||
const { jwt, local, authenticated } = require("./middleware")
|
const { jwt, local, authenticated, google } = require("./middleware")
|
||||||
|
const { setDB, getDB } = require("./db")
|
||||||
|
|
||||||
// Strategies
|
// Strategies
|
||||||
passport.use(new LocalStrategy(local.options, local.authenticate))
|
passport.use(new LocalStrategy(local.options, local.authenticate))
|
||||||
passport.use(new JwtStrategy(jwt.options, jwt.authenticate))
|
passport.use(new JwtStrategy(jwt.options, jwt.authenticate))
|
||||||
// passport.use(new GoogleStrategy(google.options, google.authenticate))
|
|
||||||
|
|
||||||
passport.serializeUser((user, done) => done(null, user))
|
passport.serializeUser((user, done) => done(null, user))
|
||||||
|
|
||||||
|
@ -36,6 +35,8 @@ module.exports = {
|
||||||
auth: {
|
auth: {
|
||||||
buildAuthMiddleware: authenticated,
|
buildAuthMiddleware: authenticated,
|
||||||
passport,
|
passport,
|
||||||
|
google,
|
||||||
},
|
},
|
||||||
|
StaticDatabases,
|
||||||
constants: require("./constants"),
|
constants: require("./constants"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
const { Cookies } = require("../constants")
|
const { Cookies } = require("../constants")
|
||||||
|
const database = require("../db")
|
||||||
const { getCookie } = require("../utils")
|
const { getCookie } = require("../utils")
|
||||||
|
const { StaticDatabases } = require("../db/utils")
|
||||||
|
|
||||||
module.exports = (noAuthPatterns = []) => {
|
module.exports = (noAuthPatterns = []) => {
|
||||||
const regex = new RegExp(noAuthPatterns.join("|"))
|
const regex = new RegExp(noAuthPatterns.join("|"))
|
||||||
|
@ -13,8 +15,11 @@ module.exports = (noAuthPatterns = []) => {
|
||||||
const authCookie = getCookie(ctx, Cookies.Auth)
|
const authCookie = getCookie(ctx, Cookies.Auth)
|
||||||
|
|
||||||
if (authCookie) {
|
if (authCookie) {
|
||||||
|
const db = database.getDB(StaticDatabases.GLOBAL.name)
|
||||||
|
const user = await db.get(authCookie.userId)
|
||||||
|
delete user.password
|
||||||
ctx.isAuthenticated = true
|
ctx.isAuthenticated = true
|
||||||
ctx.user = authCookie
|
ctx.user = user
|
||||||
}
|
}
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
|
|
|
@ -1,12 +1,76 @@
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
|
const jwt = require("jsonwebtoken")
|
||||||
|
const database = require("../../db")
|
||||||
|
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||||
|
const { StaticDatabases, generateGlobalUserID } = require("../../db/utils")
|
||||||
|
|
||||||
exports.options = {
|
async function authenticate(token, tokenSecret, profile, done) {
|
||||||
clientId: env.GOOGLE_CLIENT_ID,
|
// Check the user exists in the instance DB by email
|
||||||
clientSecret: env.GOOGLE_CLIENT_SECRET,
|
const db = database.getDB(StaticDatabases.GLOBAL.name)
|
||||||
callbackURL: env.GOOGLE_AUTH_CALLBACK_URL,
|
|
||||||
|
let dbUser
|
||||||
|
const userId = generateGlobalUserID(profile.id)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// use the google profile id
|
||||||
|
dbUser = await db.get(userId)
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Google user not found. Creating..")
|
||||||
|
// create the user
|
||||||
|
const user = {
|
||||||
|
_id: userId,
|
||||||
|
provider: profile.provider,
|
||||||
|
roles: {},
|
||||||
|
builder: {
|
||||||
|
global: true,
|
||||||
|
},
|
||||||
|
...profile._json,
|
||||||
|
}
|
||||||
|
const response = await db.post(user)
|
||||||
|
|
||||||
|
dbUser = user
|
||||||
|
dbUser._rev = response.rev
|
||||||
|
}
|
||||||
|
|
||||||
|
// authenticate
|
||||||
|
const payload = {
|
||||||
|
userId: dbUser._id,
|
||||||
|
builder: dbUser.builder,
|
||||||
|
email: dbUser.email,
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUser.token = jwt.sign(payload, env.JWT_SECRET, {
|
||||||
|
expiresIn: "1 day",
|
||||||
|
})
|
||||||
|
|
||||||
|
return done(null, dbUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
// exports.authenticate = async function(token, tokenSecret, profile, done) {
|
/**
|
||||||
// // retrieve user ...
|
* Create an instance of the google passport strategy. This wrapper fetches the configuration
|
||||||
// fetchUser().then(user => done(null, user))
|
* 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(config) {
|
||||||
|
try {
|
||||||
|
const { clientID, clientSecret, callbackURL } = config
|
||||||
|
|
||||||
|
if (!clientID || !clientSecret || !callbackURL) {
|
||||||
|
throw new Error(
|
||||||
|
"Configuration invalid. Must contain google clientID, clientSecret and callbackURL"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GoogleStrategy(
|
||||||
|
{
|
||||||
|
clientID: config.clientID,
|
||||||
|
clientSecret: config.clientSecret,
|
||||||
|
callbackURL: config.callbackURL,
|
||||||
|
},
|
||||||
|
authenticate
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
throw new Error("Error constructing google authentication strategy", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -33,8 +33,6 @@ exports.authenticate = async function(email, password, done) {
|
||||||
if (await compare(password, dbUser.password)) {
|
if (await compare(password, dbUser.password)) {
|
||||||
const payload = {
|
const payload = {
|
||||||
userId: dbUser._id,
|
userId: dbUser._id,
|
||||||
builder: dbUser.builder,
|
|
||||||
email: dbUser.email,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dbUser.token = jwt.sign(payload, env.JWT_SECRET, {
|
dbUser.token = jwt.sign(payload, env.JWT_SECRET, {
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
<Input outline type="password" on:change bind:value={password} />
|
<Input outline type="password" on:change bind:value={password} />
|
||||||
<Spacer large />
|
<Spacer large />
|
||||||
<Button primary on:click={login}>Login</Button>
|
<Button primary on:click={login}>Login</Button>
|
||||||
|
<a target="_blank" href="/api/admin/auth/google">Sign In With Google</a>
|
||||||
<Button secondary on:click={createTestUser}>Create Test User</Button>
|
<Button secondary on:click={createTestUser}>Create Test User</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ exports.createMetadata = async function(ctx) {
|
||||||
|
|
||||||
exports.updateSelfMetadata = async function(ctx) {
|
exports.updateSelfMetadata = async function(ctx) {
|
||||||
// overwrite the ID with current users
|
// overwrite the ID with current users
|
||||||
ctx.request.body._id = ctx.user.userId
|
ctx.request.body._id = ctx.user._id
|
||||||
// make sure no stale rev
|
// make sure no stale rev
|
||||||
delete ctx.request.body._rev
|
delete ctx.request.body._rev
|
||||||
await exports.updateMetadata(ctx)
|
await exports.updateMetadata(ctx)
|
||||||
|
|
|
@ -31,7 +31,7 @@ module.exports = async (ctx, next) => {
|
||||||
appCookie.roleId === BUILTIN_ROLE_IDS.PUBLIC)
|
appCookie.roleId === BUILTIN_ROLE_IDS.PUBLIC)
|
||||||
) {
|
) {
|
||||||
// Different App ID means cookie needs reset, or if the same public user has logged in
|
// Different App ID means cookie needs reset, or if the same public user has logged in
|
||||||
const globalId = getGlobalIDFromUserMetadataID(ctx.user.userId)
|
const globalId = getGlobalIDFromUserMetadataID(ctx.user._id)
|
||||||
const globalUser = await getGlobalUsers(ctx, requestAppId, globalId)
|
const globalUser = await getGlobalUsers(ctx, requestAppId, globalId)
|
||||||
updateCookie = true
|
updateCookie = true
|
||||||
appId = requestAppId
|
appId = requestAppId
|
||||||
|
@ -50,7 +50,7 @@ module.exports = async (ctx, next) => {
|
||||||
ctx.appId = appId
|
ctx.appId = appId
|
||||||
if (roleId) {
|
if (roleId) {
|
||||||
ctx.roleId = roleId
|
ctx.roleId = roleId
|
||||||
const userId = ctx.user ? generateUserMetadataID(ctx.user.userId) : null
|
const userId = ctx.user ? generateUserMetadataID(ctx.user._id) : null
|
||||||
ctx.user = {
|
ctx.user = {
|
||||||
...ctx.user,
|
...ctx.user,
|
||||||
// override userID with metadata one
|
// override userID with metadata one
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Start Server",
|
||||||
|
"program": "${workspaceFolder}/src/index.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest - All",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": [],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest - Users",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": ["user.spec", "--runInBand"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest - Instances",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": ["instance.spec", "--runInBand"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest - Roles",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": ["role.spec", "--runInBand"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest - Records",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": ["record.spec", "--runInBand"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest - Models",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": ["table.spec", "--runInBand"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest - Views",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": ["view.spec", "--runInBand"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest - Applications",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": ["application.spec", "--runInBand"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest Builder",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": ["builder", "--runInBand"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Initialise Budibase",
|
||||||
|
"program": "yarn",
|
||||||
|
"args": ["run", "initialise"],
|
||||||
|
"console": "externalTerminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
const CouchDB = require("../../../db")
|
||||||
|
const authPkg = require("@budibase/auth")
|
||||||
|
const { utils, StaticDatabases } = authPkg
|
||||||
|
|
||||||
|
const GLOBAL_DB = StaticDatabases.GLOBAL.name
|
||||||
|
|
||||||
|
exports.save = async function(ctx) {
|
||||||
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
|
const configDoc = ctx.request.body
|
||||||
|
const { type, group, user } = configDoc
|
||||||
|
|
||||||
|
// Config does not exist yet
|
||||||
|
if (!configDoc._id) {
|
||||||
|
configDoc._id = utils.generateConfigID({
|
||||||
|
type,
|
||||||
|
group,
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await db.post(configDoc)
|
||||||
|
ctx.body = {
|
||||||
|
type,
|
||||||
|
_id: response.id,
|
||||||
|
_rev: response.rev,
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ctx.throw(err.status, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.fetch = async function(ctx) {
|
||||||
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
|
const response = await db.allDocs(
|
||||||
|
utils.getConfigParams(undefined, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const groups = response.rows.map(row => row.doc)
|
||||||
|
ctx.body = groups
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the most granular config for a particular configuration type.
|
||||||
|
* The hierarchy is type -> group -> user.
|
||||||
|
*/
|
||||||
|
exports.find = async function(ctx) {
|
||||||
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
|
const userId = ctx.params.user && ctx.params.user._id
|
||||||
|
|
||||||
|
const { group } = ctx.query
|
||||||
|
if (group) {
|
||||||
|
const group = await db.get(group)
|
||||||
|
const userInGroup = group.users.some(groupUser => groupUser === userId)
|
||||||
|
if (!ctx.user.admin && !userInGroup) {
|
||||||
|
ctx.throw(400, `User is not in specified group: ${group}.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Find the config with the most granular scope based on context
|
||||||
|
const scopedConfig = await authPkg.db.determineScopedConfig(db, {
|
||||||
|
type: ctx.params.type,
|
||||||
|
user: userId,
|
||||||
|
group,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (scopedConfig) {
|
||||||
|
ctx.body = scopedConfig
|
||||||
|
} else {
|
||||||
|
ctx.throw(400, "No configuration exists.")
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ctx.throw(err.status, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.destroy = async function(ctx) {
|
||||||
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
|
const { id, rev } = ctx.params
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.remove(id, rev)
|
||||||
|
ctx.body = { message: "Config deleted successfully" }
|
||||||
|
} catch (err) {
|
||||||
|
ctx.throw(err.status, err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,8 @@
|
||||||
const { generateTemplateID, getTemplateParams, StaticDatabases } = require("@budibase/auth").db
|
const {
|
||||||
|
generateTemplateID,
|
||||||
|
getTemplateParams,
|
||||||
|
StaticDatabases,
|
||||||
|
} = require("@budibase/auth").db
|
||||||
const { CouchDB } = require("../../../db")
|
const { CouchDB } = require("../../../db")
|
||||||
const { TemplatePurposePretty, TemplateTypes, EmailTemplatePurpose, TemplatePurpose } = require("../../../constants")
|
const { TemplatePurposePretty, TemplateTypes, EmailTemplatePurpose, TemplatePurpose } = require("../../../constants")
|
||||||
const { getTemplateByPurpose } = require("../../../constants/templates")
|
const { getTemplateByPurpose } = require("../../../constants/templates")
|
||||||
|
@ -68,7 +72,7 @@ exports.save = async ctx => {
|
||||||
|
|
||||||
exports.definitions = async ctx => {
|
exports.definitions = async ctx => {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
purpose: TemplatePurposePretty
|
purpose: TemplatePurposePretty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
const authPkg = 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 { clearCookie } = authPkg.utils
|
||||||
const { Cookies } = authPkg.constants
|
const { Cookies } = authPkg.constants
|
||||||
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) {
|
||||||
|
@ -34,10 +39,55 @@ exports.logout = async ctx => {
|
||||||
ctx.body = { message: "User logged out" }
|
ctx.body = { message: "User logged out" }
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.googleAuth = async () => {
|
/**
|
||||||
// return passport.authenticate("google")
|
* The initial call that google authentication makes to take you to the google login screen.
|
||||||
|
* On a successful login, you will be redirected to the googleAuth callback route.
|
||||||
|
*/
|
||||||
|
exports.googlePreAuth = async (ctx, next) => {
|
||||||
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
|
const config = await authPkg.db.determineScopedConfig(db, {
|
||||||
|
type: Configs.GOOGLE,
|
||||||
|
group: ctx.query.group,
|
||||||
|
})
|
||||||
|
const strategy = await google.strategyFactory(config)
|
||||||
|
|
||||||
|
return passport.authenticate(strategy, {
|
||||||
|
scope: ["profile", "email"],
|
||||||
|
})(ctx, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.googleAuth = async () => {
|
exports.googleAuth = async (ctx, next) => {
|
||||||
// return passport.authenticate("google")
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
|
|
||||||
|
const config = await authPkg.db.determineScopedConfig(db, {
|
||||||
|
type: Configs.GOOGLE,
|
||||||
|
group: ctx.query.group,
|
||||||
|
})
|
||||||
|
const strategy = await google.strategyFactory(config)
|
||||||
|
|
||||||
|
return passport.authenticate(
|
||||||
|
strategy,
|
||||||
|
{ successRedirect: "/", failureRedirect: "/error" },
|
||||||
|
async (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
return ctx.throw(403, "Unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
const expires = new Date()
|
||||||
|
expires.setDate(expires.getDate() + 1)
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return ctx.throw(403, "Unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.cookies.set(Cookies.Auth, user.token, {
|
||||||
|
expires,
|
||||||
|
path: "/",
|
||||||
|
httpOnly: false,
|
||||||
|
overwrite: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.redirect("/")
|
||||||
|
}
|
||||||
|
)(ctx, next)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
const Router = require("@koa/router")
|
||||||
|
const controller = require("../../controllers/admin/configs")
|
||||||
|
const joiValidator = require("../../../middleware/joi-validator")
|
||||||
|
const Joi = require("joi")
|
||||||
|
const { Configs } = require("../../../constants")
|
||||||
|
|
||||||
|
const router = Router()
|
||||||
|
|
||||||
|
function buildConfigSaveValidation() {
|
||||||
|
// prettier-ignore
|
||||||
|
return joiValidator.body(Joi.object({
|
||||||
|
type: Joi.string().valid(...Object.values(Configs)).required(),
|
||||||
|
}).required().unknown(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
router
|
||||||
|
.post("/api/admin/configs", buildConfigSaveValidation(), controller.save)
|
||||||
|
.delete("/api/admin/configs/:id", controller.destroy)
|
||||||
|
.get("/api/admin/configs", controller.fetch)
|
||||||
|
.get("/api/admin/configs/:type", controller.find)
|
||||||
|
|
||||||
|
module.exports = router
|
|
@ -21,11 +21,7 @@ function buildTemplateSaveValidation() {
|
||||||
|
|
||||||
router
|
router
|
||||||
.get("/api/admin/template/definitions", controller.definitions)
|
.get("/api/admin/template/definitions", controller.definitions)
|
||||||
.post(
|
.post("/api/admin/template", buildTemplateSaveValidation(), controller.save)
|
||||||
"/api/admin/template",
|
|
||||||
buildTemplateSaveValidation(),
|
|
||||||
controller.save
|
|
||||||
)
|
|
||||||
.get("/api/admin/template", controller.fetch)
|
.get("/api/admin/template", controller.fetch)
|
||||||
.get("/api/admin/template/:type", controller.fetchByType)
|
.get("/api/admin/template/:type", controller.fetchByType)
|
||||||
.get("/api/admin/template/:ownerId", controller.fetchByOwner)
|
.get("/api/admin/template/:ownerId", controller.fetchByOwner)
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
const Router = require("@koa/router")
|
const Router = require("@koa/router")
|
||||||
const { passport } = require("@budibase/auth").auth
|
|
||||||
const authController = require("../controllers/auth")
|
const authController = require("../controllers/auth")
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
router
|
router
|
||||||
.post("/api/admin/auth", authController.authenticate)
|
.post("/api/admin/auth", authController.authenticate)
|
||||||
|
.get("/api/admin/auth/google", authController.googlePreAuth)
|
||||||
|
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
||||||
.post("/api/admin/auth/logout", authController.logout)
|
.post("/api/admin/auth/logout", authController.logout)
|
||||||
.get("/api/auth/google", passport.authenticate("google"))
|
|
||||||
.get(
|
|
||||||
"/api/auth/google/callback",
|
|
||||||
passport.authenticate("google", {
|
|
||||||
successRedirect: "/app",
|
|
||||||
failureRedirect: "/",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const userRoutes = require("./admin/users")
|
const userRoutes = require("./admin/users")
|
||||||
|
const configRoutes = require("./admin/configs")
|
||||||
const groupRoutes = require("./admin/groups")
|
const groupRoutes = require("./admin/groups")
|
||||||
const authRoutes = require("./auth")
|
const authRoutes = require("./auth")
|
||||||
const appRoutes = require("./app")
|
const appRoutes = require("./app")
|
||||||
|
|
||||||
exports.routes = [userRoutes, groupRoutes, authRoutes, appRoutes]
|
exports.routes = [configRoutes, userRoutes, groupRoutes, authRoutes, appRoutes]
|
||||||
|
|
|
@ -7,6 +7,13 @@ exports.Groups = {
|
||||||
ALL_USERS: "all_users",
|
ALL_USERS: "all_users",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.Configs = {
|
||||||
|
SETTINGS: "settings",
|
||||||
|
ACCOUNT: "account",
|
||||||
|
SMTP: "smtp",
|
||||||
|
GOOGLE: "google",
|
||||||
|
}
|
||||||
|
|
||||||
const TemplateTypes = {
|
const TemplateTypes = {
|
||||||
EMAIL: "email",
|
EMAIL: "email",
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue