forked from tsai/budibase
committed by
GitHub
18 changed files with 479 additions and 44 deletions
@ -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) |
||||
|
} |
||||
|
} |
||||
|
|||||
@ -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 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] |
||||
|
|||||
Loading…
Reference in new issue