mirror of https://github.com/status-im/codimd.git
feat: add organizations whitelist to GitHub OAuth
Currently CodiMD does not support limiting access of GitHub OAuth users based on their organization membership. This is a very useful functionality for teams that want to limit write access to their notes. I've implemented a crude solution to this problem, which most probably requires some adjusments to make it better. I'm not sure if this implementation is kosher, but it definitely works on my deployment. Open to suggestions on how I can improve it. Signed-off-by: Jakub Sokołowski <jakub@status.im>
This commit is contained in:
parent
3b1e270952
commit
0d1cd1d2f0
8
app.json
8
app.json
|
@ -80,6 +80,14 @@
|
||||||
"description": "GitHub API client secret",
|
"description": "GitHub API client secret",
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
|
"CMD_GITHUB_ORGANIZATIONS": {
|
||||||
|
"description": "GitHub whitelist of orgs",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"CMD_GITHUB_SCOPES": {
|
||||||
|
"description": "GitHub OAuth API scopes",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
"CMD_BITBUCKET_CLIENTID": {
|
"CMD_BITBUCKET_CLIENTID": {
|
||||||
"description": "Bitbucket API client id",
|
"description": "Bitbucket API client id",
|
||||||
"required": false
|
"required": false
|
||||||
|
|
|
@ -54,6 +54,8 @@
|
||||||
"github": {
|
"github": {
|
||||||
"clientID": "change this",
|
"clientID": "change this",
|
||||||
"clientSecret": "change this"
|
"clientSecret": "change this"
|
||||||
|
"organizations": ["names of github organizations allowed, optional"],
|
||||||
|
"scopes": ["defaults to 'read:user' scope for auth user"],
|
||||||
},
|
},
|
||||||
"gitlab": {
|
"gitlab": {
|
||||||
"baseURL": "change this",
|
"baseURL": "change this",
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const Router = require('express').Router
|
const Router = require('express').Router
|
||||||
|
const request = require('request')
|
||||||
const passport = require('passport')
|
const passport = require('passport')
|
||||||
const GithubStrategy = require('passport-github').Strategy
|
const GithubStrategy = require('passport-github').Strategy
|
||||||
|
const { InternalOAuthError } = require('passport-oauth2')
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
const response = require('../../response')
|
const response = require('../../response')
|
||||||
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
|
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
|
||||||
const { URL } = require('url')
|
const { URL } = require('url')
|
||||||
|
const { promisify } = require('util')
|
||||||
|
|
||||||
|
const rp = promisify(request)
|
||||||
|
|
||||||
const githubAuth = module.exports = Router()
|
const githubAuth = module.exports = Router()
|
||||||
|
|
||||||
|
@ -15,20 +20,47 @@ function githubUrl (path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
passport.use(new GithubStrategy({
|
passport.use(new GithubStrategy({
|
||||||
|
scope: (config.github.organizations ?
|
||||||
|
config.github.scopes.concat(['read:org']) : config.github.scope),
|
||||||
clientID: config.github.clientID,
|
clientID: config.github.clientID,
|
||||||
clientSecret: config.github.clientSecret,
|
clientSecret: config.github.clientSecret,
|
||||||
callbackURL: config.serverURL + '/auth/github/callback',
|
callbackURL: config.serverURL + '/auth/github/callback',
|
||||||
authorizationURL: githubUrl('login/oauth/authorize'),
|
authorizationURL: githubUrl('login/oauth/authorize'),
|
||||||
tokenURL: githubUrl('login/oauth/access_token'),
|
tokenURL: githubUrl('login/oauth/access_token'),
|
||||||
userProfileURL: githubUrl('api/v3/user')
|
userProfileURL: githubUrl('api/v3/user')
|
||||||
}, passportGeneralCallback))
|
}, async (accessToken, refreshToken, profile, done) => {
|
||||||
|
if (!config.github.organizations) {
|
||||||
|
return passportGeneralCallback(accessToken, refreshToken, profile, done)
|
||||||
|
}
|
||||||
|
const { statusCode, body: data } = await rp({
|
||||||
|
url: `https://api.github.com/user/orgs`,
|
||||||
|
method: 'GET', json: true, timeout: 2000,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `token ${accessToken}`,
|
||||||
|
'User-Agent': 'nodejs-http'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (statusCode != 200) {
|
||||||
|
return done(InternalOAuthError(
|
||||||
|
`Failed to query organizations for user: ${profile.username}`
|
||||||
|
))
|
||||||
|
}
|
||||||
|
const orgs = data.map(({login}) => login)
|
||||||
|
for (const org of orgs) {
|
||||||
|
if (config.github.organizations.includes(org)) {
|
||||||
|
return passportGeneralCallback(accessToken, refreshToken, profile, done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return done(InternalOAuthError(
|
||||||
|
`User orgs not whitelisted: ${profile.username} (${orgs.join(',')})`
|
||||||
|
))
|
||||||
|
}))
|
||||||
|
|
||||||
githubAuth.get('/auth/github', function (req, res, next) {
|
githubAuth.get('/auth/github', function (req, res, next) {
|
||||||
setReturnToFromReferer(req)
|
setReturnToFromReferer(req)
|
||||||
passport.authenticate('github')(req, res, next)
|
passport.authenticate('github')(req, res, next)
|
||||||
})
|
})
|
||||||
|
|
||||||
// github auth callback
|
|
||||||
githubAuth.get('/auth/github/callback',
|
githubAuth.get('/auth/github/callback',
|
||||||
passport.authenticate('github', {
|
passport.authenticate('github', {
|
||||||
successReturnToOrRedirect: config.serverURL + '/',
|
successReturnToOrRedirect: config.serverURL + '/',
|
||||||
|
|
|
@ -115,7 +115,9 @@ module.exports = {
|
||||||
github: {
|
github: {
|
||||||
enterpriseURL: undefined, // if you use github.com, not need to specify
|
enterpriseURL: undefined, // if you use github.com, not need to specify
|
||||||
clientID: undefined,
|
clientID: undefined,
|
||||||
clientSecret: undefined
|
clientSecret: undefined,
|
||||||
|
organizations: [],
|
||||||
|
scopes: ['read:user']
|
||||||
},
|
},
|
||||||
gitlab: {
|
gitlab: {
|
||||||
baseURL: undefined,
|
baseURL: undefined,
|
||||||
|
|
|
@ -69,7 +69,9 @@ module.exports = {
|
||||||
github: {
|
github: {
|
||||||
enterpriseURL: process.env.CMD_GITHUB_ENTERPRISE_URL,
|
enterpriseURL: process.env.CMD_GITHUB_ENTERPRISE_URL,
|
||||||
clientID: process.env.CMD_GITHUB_CLIENTID,
|
clientID: process.env.CMD_GITHUB_CLIENTID,
|
||||||
clientSecret: process.env.CMD_GITHUB_CLIENTSECRET
|
clientSecret: process.env.CMD_GITHUB_CLIENTSECRET,
|
||||||
|
organizations: toArrayConfig(process.env.CMD_GITHUB_ORGANIZATIONS),
|
||||||
|
scopes: toArrayConfig(process.env.CMD_GITHUB_SCOPES),
|
||||||
},
|
},
|
||||||
bitbucket: {
|
bitbucket: {
|
||||||
clientID: process.env.CMD_BITBUCKET_CLIENTID,
|
clientID: process.env.CMD_BITBUCKET_CLIENTID,
|
||||||
|
|
|
@ -80,6 +80,14 @@
|
||||||
"description": "GitHub API client secret",
|
"description": "GitHub API client secret",
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
|
"CMD_GITHUB_ORGANIZATIONS": {
|
||||||
|
"description": "GitHub whitelist of orgs",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"CMD_GITHUB_SCOPES": {
|
||||||
|
"description": "GitHub OAuth API scopes",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
"CMD_BITBUCKET_CLIENTID": {
|
"CMD_BITBUCKET_CLIENTID": {
|
||||||
"description": "Bitbucket API client id",
|
"description": "Bitbucket API client id",
|
||||||
"required": false
|
"required": false
|
||||||
|
|
Loading…
Reference in New Issue