diff --git a/controllers/auth.js b/controllers/auth.js index 812933c09..9eb040bc8 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -15,6 +15,7 @@ var User = require('../models/user').User; var verifyPassport = require('../libs/passportVerify').verify; var cleanFilename = require('../libs/helpers').cleanFilename; var addSession = require('../libs/modifySessions').add; +var jwt = require('jwt-simple'); // Unused but removing it breaks passport passport.serializeUser(function (aUser, aDone) { @@ -43,16 +44,22 @@ exports.auth = function (aReq, aRes, aNext) { var authedUser = aReq.session.user; var strategy = aReq.body.auth || aReq.params.strategy; var username = aReq.body.username || aReq.session.username; + var authOpts = { failureRedirect: '/register?stratfail' }; function auth() { - var authenticate = passport.authenticate(strategy, { failureRedirect: '/register?stratfail' }); + var authenticate = null; + + if (strategy === 'google') { + authOpts.scope = ['https://www.googleapis.com/auth/userinfo.profile']; + } + authenticate = passport.authenticate(strategy, authOpts); // Just in case some dumbass tries a bad /auth/* url if (!strategyInstances[strategy]) { return aNext(); } - authenticate(aReq, aRes); + authenticate(aReq, aRes, aNext); } // Allow a logged in user to add a new strategy @@ -128,6 +135,14 @@ exports.callback = function (aReq, aRes, aNext) { strategyInstance._verify = function (aId, aDone) { verifyPassport(aId, strategy, username, aReq.session.user, aDone); }; + } else if (strategy === 'google') { // OpenID to OAuth2 migration + strategyInstance._verify = + function(aAccessToken, aRefreshToken, aParams, aProfile, aDone) { + var openIdId = jwt.decode(aParams.id_token, null, true).openid_id; + var oAuthId = aProfile.id; + + verifyPassport([openIdId, oAuthId], strategy, username, aReq.session.user, aDone); + }; } else { strategyInstance._verify = function (aToken, aRefreshOrSecretToken, aProfile, aDone) { diff --git a/controllers/strategies.json b/controllers/strategies.json index 802308f9a..2aecc5e2b 100644 --- a/controllers/strategies.json +++ b/controllers/strategies.json @@ -25,7 +25,7 @@ }, "google": { "name": "Google", - "oauth": false + "oauth": true }, "imgur": { "name": "Imgur", diff --git a/libs/passportLoader.js b/libs/passportLoader.js index d57335be9..eeb48ccd0 100644 --- a/libs/passportLoader.js +++ b/libs/passportLoader.js @@ -21,9 +21,12 @@ exports.strategyInstances = nil(); // This will load a single passport // Notice it is general so it can load any passport strategy exports.loadPassport = function (aStrategy) { - var requireStr = 'passport-' + aStrategy.name; - var PassportStrategy = require(requireStr).Strategy; + var requireStr = 'passport-' + aStrategy.name + + (aStrategy.name === 'google' ? '-oauth' : ''); + var PassportStrategy = require(requireStr)[ + aStrategy.name === 'google' ? 'OAuth2Strategy' : 'Strategy']; var instance = null; + var authParams = null; if (aStrategy.openid) { instance = new PassportStrategy( @@ -50,6 +53,15 @@ exports.loadPassport = function (aStrategy) { ); } + if (aStrategy.name === 'google') { + authParams = instance.authorizationParams; + instance.authorizationParams = function() { + var val = authParams.apply(this, arguments); + val['openid.realm'] = AUTH_CALLBACK_BASE_URL + '/'; + return val; + }; + } + exports.strategyInstances[aStrategy.name] = instance; passport.use(instance); }; diff --git a/libs/passportVerify.js b/libs/passportVerify.js index 6ff7097e9..a9bf5b816 100644 --- a/libs/passportVerify.js +++ b/libs/passportVerify.js @@ -16,9 +16,18 @@ var userRoles = require('../models/userRoles.json'); exports.verify = function (aId, aStrategy, aUsername, aLoggedIn, aDone) { var shasum = crypto.createHash('sha256'); var digest = null; + var query = {}; + var ids = []; - // We only keep plaintext ids for GH since that's all we need - if (aStrategy === 'github') { + if (aId instanceof Array) { + ids = aId.map(function (aId) { + var shasum = crypto.createHash('sha256'); + shasum.update(String(aId)); + return shasum.digest('hex'); + }); + query.auths = { '$in': ids }; + } else if (aStrategy === 'github') { + // We only keep plaintext ids for GH since that's all we need digest = aId; } else { // Having these ids would allow us to do things with the user's @@ -27,11 +36,25 @@ exports.verify = function (aId, aStrategy, aUsername, aLoggedIn, aDone) { digest = shasum.digest('hex'); } - findDeadorAlive(User, { 'auths': digest }, true, + if (!query.auths) { + query.auths = digest; + } + + findDeadorAlive(User, query, true, function (aAlive, aUser, aRemoved) { var pos = aUser ? aUser.auths.indexOf(digest) : -1; + var opendIdPos = -1; if (aRemoved) { aDone(null, false, 'user was removed'); } + // Set up for OpenId to OAuth Migration + if (!digest && ids.length > 0) { + digest = ids[1]; + if (aUser) { + pos = aUser.auths.indexOf(digest); + opendIdPos = aUser.auths.indexOf(ids[0]); + } + } + if (!aUser) { User.findOne({ 'name': aUsername }, function (aErr, aUser) { if (aUser && aLoggedIn) { @@ -69,6 +92,12 @@ exports.verify = function (aId, aStrategy, aUsername, aLoggedIn, aDone) { aUser.save(function (aErr, aUser) { return aDone(aErr, aUser); }); + } else if (opendIdPos > 0) { + // Migrate from OpenID to OAuth + aUser.auths[opendIdPos] = digest; + aUser.save(function (aErr, aUser) { + return aDone(aErr, aUser); + }); } else { // The user was authenticated return aDone(null, aUser); diff --git a/package.json b/package.json index 06013f689..241f42c25 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "github": "0.2.3", "highlight.js": "8.4.0", "jquery": "2.1.3", + "jwt-simple": "0.2.0", "less-middleware": "1.0.4", "marked": "0.3.3", "method-override": "2.3.1", @@ -36,7 +37,7 @@ "passport-flickr": "0.2.0", "passport-foursquare": "1.0.0", "passport-github": "0.1.5", - "passport-google": "0.3.0", + "passport-google-oauth": "0.1.5", "passport-imgur": "0.0.2", "passport-instagram": "0.1.2", "passport-linkedin": "0.1.3",