mirror of https://github.com/Budibase/budibase.git
committed by
GitHub
18 changed files with 479 additions and 44 deletions
@ -1,12 +1,76 @@ |
|||
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 = { |
|||
clientId: env.GOOGLE_CLIENT_ID, |
|||
clientSecret: env.GOOGLE_CLIENT_SECRET, |
|||
callbackURL: env.GOOGLE_AUTH_CALLBACK_URL, |
|||
async function authenticate(token, tokenSecret, profile, done) { |
|||
// Check the user exists in the instance DB by email
|
|||
const db = database.getDB(StaticDatabases.GLOBAL.name) |
|||
|
|||
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 ...
|
|||
// fetchUser().then(user => done(null, user))
|
|||
// }
|
|||
/** |
|||
* Create an instance of the google passport strategy. This wrapper fetches the configuration |
|||
* 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) |
|||
} |
|||
} |
|||
|
|||
@ -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) |
|||
} |
|||
} |
|||
@ -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 |
|||
@ -1,19 +1,12 @@ |
|||
const Router = require("@koa/router") |
|||
const { passport } = require("@budibase/auth").auth |
|||
const authController = require("../controllers/auth") |
|||
|
|||
const router = Router() |
|||
|
|||
router |
|||
.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) |
|||
.get("/api/auth/google", passport.authenticate("google")) |
|||
.get( |
|||
"/api/auth/google/callback", |
|||
passport.authenticate("google", { |
|||
successRedirect: "/app", |
|||
failureRedirect: "/", |
|||
}) |
|||
) |
|||
|
|||
module.exports = router |
|||
|
|||
@ -1,6 +1,7 @@ |
|||
const userRoutes = require("./admin/users") |
|||
const configRoutes = require("./admin/configs") |
|||
const groupRoutes = require("./admin/groups") |
|||
const authRoutes = require("./auth") |
|||
const appRoutes = require("./app") |
|||
|
|||
exports.routes = [userRoutes, groupRoutes, authRoutes, appRoutes] |
|||
exports.routes = [configRoutes, userRoutes, groupRoutes, authRoutes, appRoutes] |
|||
|
|||
Loading…
Reference in new issue