diff --git a/app.json b/app.json index 7c43a3da..b30d4876 100644 --- a/app.json +++ b/app.json @@ -80,6 +80,14 @@ "description": "GitHub API client secret", "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": { "description": "Bitbucket API client id", "required": false diff --git a/config.json.example b/config.json.example index 11422652..ea87aa2c 100644 --- a/config.json.example +++ b/config.json.example @@ -54,6 +54,8 @@ "github": { "clientID": "change this", "clientSecret": "change this" + "organizations": ["names of github organizations allowed, optional"], + "scopes": ["defaults to 'read:user' scope for auth user"], }, "gitlab": { "baseURL": "change this", diff --git a/lib/auth/github/index.js b/lib/auth/github/index.js index 8f05f122..fed850b3 100644 --- a/lib/auth/github/index.js +++ b/lib/auth/github/index.js @@ -1,12 +1,17 @@ 'use strict' const Router = require('express').Router +const request = require('request') const passport = require('passport') const GithubStrategy = require('passport-github').Strategy +const { InternalOAuthError } = require('passport-oauth2') const config = require('../../config') const response = require('../../response') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils') const { URL } = require('url') +const { promisify } = require('util') + +const rp = promisify(request) const githubAuth = module.exports = Router() @@ -15,20 +20,47 @@ function githubUrl (path) { } passport.use(new GithubStrategy({ + scope: (config.github.organizations ? + config.github.scopes.concat(['read:org']) : config.github.scope), clientID: config.github.clientID, clientSecret: config.github.clientSecret, callbackURL: config.serverURL + '/auth/github/callback', authorizationURL: githubUrl('login/oauth/authorize'), tokenURL: githubUrl('login/oauth/access_token'), 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) { setReturnToFromReferer(req) passport.authenticate('github')(req, res, next) }) -// github auth callback githubAuth.get('/auth/github/callback', passport.authenticate('github', { successReturnToOrRedirect: config.serverURL + '/', diff --git a/lib/config/default.js b/lib/config/default.js index 488363b6..d276e46e 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -115,7 +115,9 @@ module.exports = { github: { enterpriseURL: undefined, // if you use github.com, not need to specify clientID: undefined, - clientSecret: undefined + clientSecret: undefined, + organizations: [], + scopes: ['read:user'] }, gitlab: { baseURL: undefined, diff --git a/lib/config/environment.js b/lib/config/environment.js index b0124a0d..5515d23c 100644 --- a/lib/config/environment.js +++ b/lib/config/environment.js @@ -69,7 +69,9 @@ module.exports = { github: { enterpriseURL: process.env.CMD_GITHUB_ENTERPRISE_URL, 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: { clientID: process.env.CMD_BITBUCKET_CLIENTID, diff --git a/scalingo.json b/scalingo.json index ab93f031..e3b86301 100644 --- a/scalingo.json +++ b/scalingo.json @@ -80,6 +80,14 @@ "description": "GitHub API client secret", "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": { "description": "Bitbucket API client id", "required": false