diff --git a/lib/cli.js b/lib/cli.js index 4052bd60..10465601 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -329,6 +329,98 @@ program }) }) +program + .command('auth-oauth2') + .description('start OAuth 2.0 auth client') + .option('-p, --port ' + , 'port (or $PORT)' + , Number + , process.env.PORT || 7120) + .option('-s, --secret ' + , 'secret (or $SECRET)' + , String + , process.env.SECRET) + .option('-i, --ssid ' + , 'session SSID (or $SSID)' + , String + , process.env.SSID || 'ssid') + .option('-a, --app-url ' + , 'URL to app' + , String) + .option('--oauth-authorization-url ' + , 'OAuth 2.0 authorization URL (or $OAUTH_AUTHORIZATION_URL)' + , String + , process.env.OAUTH_AUTHORIZATION_URL) + .option('--oauth-token-url ' + , 'OAuth 2.0 token URL (or $OAUTH_TOKEN_URL)' + , String + , process.env.OAUTH_TOKEN_URL) + .option('--oauth-userinfo-url ' + , 'OAuth 2.0 token URL (or $OAUTH_USERINFO_URL)' + , String + , process.env.OAUTH_USERINFO_URL) + .option('--oauth-client-id ' + , 'OAuth 2.0 client ID (or $OAUTH_CLIENT_ID)' + , String + , process.env.OAUTH_CLIENT_ID) + .option('--oauth-client-secret ' + , 'OAuth 2.0 client secret (or $OAUTH_CLIENT_SECRET)' + , String + , process.env.OAUTH_CLIENT_SECRET) + .option('--oauth-callback-url ' + , 'OAuth 2.0 callback URL (or $OAUTH_CALLBACK_URL)' + , String + , process.env.OAUTH_CALLBACK_URL) + .option('--oauth-scope ' + , 'Space-separated OAuth 2.0 scope (or $OAUTH_SCOPE)' + , String + , process.env.OAUTH_SCOPE) + .action(function(options) { + if (!options.secret) { + this.missingArgument('--secret') + } + if (!options.appUrl) { + this.missingArgument('--app-url') + } + if (!options.oauthAuthorizationUrl) { + this.missingArgument('--oauth-authorization-url') + } + if (!options.oauthTokenUrl) { + this.missingArgument('--oauth-token-url') + } + if (!options.oauthUserinfoUrl) { + this.missingArgument('--oauth-userinfo-url') + } + if (!options.oauthClientId) { + this.missingArgument('--oauth-client-id') + } + if (!options.oauthClientSecret) { + this.missingArgument('--oauth-client-secret') + } + if (!options.oauthCallbackUrl) { + this.missingArgument('--oauth-callback-url') + } + if (!options.oauthScope) { + this.missingArgument('--oauth-scope') + } + + require('./units/auth/oauth2')({ + port: options.port + , secret: options.secret + , ssid: options.ssid + , appUrl: options.appUrl + , oauth: { + authorizationURL: options.oauthAuthorizationUrl + , tokenURL: options.oauthTokenUrl + , userinfoURL: options.oauthUserinfoUrl + , clientID: options.oauthClientId + , clientSecret: options.oauthClientSecret + , callbackURL: options.oauthCallbackUrl + , scope: options.oauthScope.split(/\s+/) + } + }) + }) + program .command('auth-mock') .description('start mock auth client') diff --git a/lib/units/auth/oauth2/index.js b/lib/units/auth/oauth2/index.js new file mode 100644 index 00000000..d3a21942 --- /dev/null +++ b/lib/units/auth/oauth2/index.js @@ -0,0 +1,51 @@ +var http = require('http') + +// @todo Figure something out +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" + +var express = require('express') +var passport = require('passport') + +var logger = require('../../../util/logger') +var urlutil = require('../../../util/urlutil') +var jwtutil = require('../../../util/jwtutil') +var Strategy = require('./strategy') + +module.exports = function(options) { + var log = logger.createLogger('auth-oauth2') + , app = express() + , server = http.createServer(app) + + app.set('strict routing', true) + app.set('case sensitive routing', true) + + function verify(accessToken, refreshToken, profile, done) { + done(null, profile) + } + + passport.use(new Strategy(options.oauth, verify)) + + app.use(passport.initialize()) + app.use(passport.authenticate('oauth2', { + failureRedirect: '/auth/oauth/' + , session: false + })) + + app.get( + '/auth/oauth/callback' + , function(req, res) { + res.redirect(urlutil.addParams(options.appUrl, { + jwt: jwtutil.encode({ + payload: { + email: req.user.email + , name: req.user.email.split('@', 1).join('') + } + , secret: options.secret + }) + })) + } + ) + + server.listen(options.port) + log.info('Listening on port %d', options.port) +} diff --git a/lib/units/auth/oauth2/strategy.js b/lib/units/auth/oauth2/strategy.js new file mode 100644 index 00000000..9f36041f --- /dev/null +++ b/lib/units/auth/oauth2/strategy.js @@ -0,0 +1,32 @@ +var util = require('util') + +var oauth2 = require('passport-oauth2') + +function Strategy(options, verify) { + oauth2.Strategy.call(this, options, verify) + if (!options.authorizationURL) { + throw new TypeError('OAuth2Strategy requires a userinfoURL option') + } + this._userinfoURL = options.userinfoURL + this._oauth2.useAuthorizationHeaderforGET(true) +} + +util.inherits(Strategy, oauth2.Strategy) + +Strategy.prototype.userProfile = function(accessToken, callback) { + this._oauth2.get(this._userinfoURL, accessToken, function(err, data) { + if (err) { + return callback(err) + } + else { + try { + return callback(null, JSON.parse(data)) + } + catch (err) { + return callback(err) + } + } + }) +} + +module.exports = Strategy