UNPKG

guardian

Version:

Authentication Gateway Proxy

342 lines (282 loc) 10.6 kB
#!/usr/bin/env node // jshint laxbreak: true // Global Requirements var cluster = require('cluster'), ascii = require('asciimo').Figlet, colors = require('colors'), winston = require('winston'), fs = require('fs'), args = require('optimist').options('c', { "alias": 'config', "default": false }).argv, configLocation = args.config ? [process.cwd(), args.config].join('/') : './config/default', config = require(configLocation); // Logging Setup var log = new (winston.Logger)({ transports: [ new (winston.transports.Console)() ] }); // Start Master and Workers ascii.write("guardian", "Thick", function (art) { if (cluster.isMaster) { var i = 0; var pidPath; // Load Banner console.info("\n" + art.rainbow); // Load configuration console.info("Configuration loaded from:", configLocation); // Generate PID file pidPath = config.pid.dir + ".guardian.pid"; console.info(("Master started with PID " + process.pid + ", saved at: " + pidPath).grey); fs.writeFileSync(pidPath, process.pid, 'utf8'); // Output port server is running on. console.info("Starting server on port ", config.port); // Cluster guardian instance for (i; i < config.workers; i++) cluster.fork(); } else { // Core requirements var gate = require('./lib/core'), keeper; // Requires var express = require('express'), redis = require('redis'), query = require('querystring'), utils = require('mashape-oauth').utils, nuu = require('nuuid'), http = require('http'), https = require('https'), url = require('url'); // Setup Express & Logger var app = express(); // Setup Redis Storage for Sessions var RedisOptions = {}; if (config.redis.pass) RedisOptions.no_ready_check = true; var RedisStore = require('connect-redis')(express); var RedisClient = redis.createClient(config.redis.port, config.redis.host, RedisOptions); var RedisSession = new RedisStore({ client: RedisClient }); if (config.redis.pass) { RedisClient.auth(config.redis.pass, function () { log.info('Authenticated redis client!'); }); } // Configuration app.configure(function () { app.set('view engine', 'ejs'); app.set('views', __dirname + '/_views'); app.use(express.static(__dirname + '/_assets')); app.use(express.bodyParser()); app.use(express.cookieParser(config.cookie.secret)); app.use(express.session({ store: RedisSession, key: 'gate.keeper', secret: config.session.secret })); app.use(function (req, res, next) { res.header("X-powered-by", "Guardian, the last Gatekeeper."); next(); }); }); app.get('/ping', function(req, res) { res.status(200); res.type('json').send({ping: "pong"}); }); app.post('/store', function (req, res) { var plugin; var error; var opts; var path; var id; // Generate options object opts = { clientId: req.param('client_id'), clientSecret: req.param('client_secret'), consumerKey: req.param('consumer_key'), consumerSecret: req.param('consumer_secret'), grantType: req.param('grant_type'), state: req.param('state'), scope: req.param('scope'), baseUrl: req.param('base_url'), requestUrl: req.param('request_url'), accessUrl: req.param('access_url'), accessName: req.param('access_name'), authorizeUrl: req.param('authorize_url'), authorizeMethod: req.param('authorize_method'), signatureMethod: req.param('signature_method'), oauth_token: req.param('oauth_token'), callbackUrl: req.param('redirect') ? req.param('redirect') : config.protocol + '://' + config.host + '/callback', version: req.param('version'), type: req.param('type'), auth: { type: (req.param('auth_type') || 'oauth').replace(/[^a-z]/g, ''), flow: (req.param('auth_flow') || '').replace(/[^a-z\_]/g, ''), version: isNaN(parseInt(req.param('auth_version'), 10)) ? false : parseInt(req.param('auth_version'), 10), leg: isNaN(parseInt(req.param('auth_leg'), 10)) ? false : parseInt(req.param('auth_leg'), 10) }, done: { callback: req.param('callback') } }; // 1. Load plugin // 2. Run validation // 3. Store options object try { path = gate.generatePluginPath(opts.auth); plugin = gate.requirePlugin(opts.auth); error = plugin.validate(opts); // Check plugin if (!plugin) { throw new Error("PLUGIN_MISSING"); } // Check plugin validation if (error) { return res.jsonp(500, { code: 'PLUGIN_VALIDATION', message: error }); } // Convert underscores in signature method to dashes if (opts.signatureMethod && opts.signatureMethod.indexOf("_")) opts.signatureMethod = opts.signatureMethod.replace('_', '-'); // Generate hash id = nuu.id(opts.consumerKey); // Store options in redis with hash identifier RedisClient.set(id, JSON.stringify(opts), redis.print); RedisClient.expire(id, config.redis.expire); console.log(id); // Generate output res.jsonp({ hash: id, url: config.protocol + '://' + config.host + '/start?hash=' + id }); } catch (e) { if (e.message === 'PLUGIN_MISSING') { return res.jsonp(500, { code: 'PLUGIN_MISSING', message: 'Invalid plugin specified, could not find plugin: ' + opts.auth.type.toLowerCase() + path.flow + path.version + path.leg }); } return res.jsonp(500, { code: 'ERROR', message: e.message }); } }); app.get('/hash-check', function (req, res) { RedisClient.get(req.param('hash'), function (err, reply) { if (err) return res.send(500, err.message); res.json(JSON.parse(reply)); }); }); app.all('/start', function (req, res) { var original = req.param('hash'); var hash = (typeof original === 'string' ? original : '').replace(/[^a-z0-9\-]/gi, ''); if (hash != original) { return res.json(500, { code: 'INVALID_HASH', message: 'Invalid hash sequence given. Please check hash and try again.' }); } RedisClient.get(hash, function (err, reply) { if (err) { return res.json(500, { code: 'ERROR', message: err.message }); } if (!reply) { return res.json(500, { code: 'EXPIRED_HASH', message: 'Hash (' + hash + ') has been expired.' }); } // Retrieve session data req.session.data = JSON.parse(reply); // Setup data from response on session data object if (req.param('url')) req.session.data.call_url = req.param('url'); if (req.param('method')) req.session.data.call_method = req.param('method'); if (req.param('body')) req.session.data.call_body = req.param('body'); if (req.param('parameters')) req.session.data.parameters = req.param('parameters'); // Invoke guardian gatekeeper keeper = gate({ req: req, res: res }); keeper.invokeStep(1); }); }); app.get('/step/:number', function (req, res) { keeper = gate({ req: req, res: res }); keeper.invokeStep(parseInt(req.params.number, 10)); }); app.get('/callback', function (req, res) { var plugin; var step; var data; var args; if (!req.session.data) { return res.json(500, { code: 'MISSING_SESSION_DETAILS', message: 'Session details are missing, perhaps the redirect url is on another server or the request timed out.' }); } // Parse session data data = JSON.parse(JSON.stringify(req.session.data)); log.info(data); // Include plugin plugin = gate.requirePlugin(data.auth); if (!plugin.step.callback) { return res.json(500, { code: 'MISSING_CALLBACK_STEP', message: 'Callback step is missing, unable to continue authentication process.' }); } // Fetch information from previously set data. step = req.session.data.step; args = {}; // Retrieve information returned from authentication step if (req.param('oauth_token')) args.token = req.param('oauth_token'); if (req.param('oauth_verifier')) args.verifier = req.param('oauth_verifier'); if (req.param('code')) args.code = req.param('code'); if (req.param('state')) args.state = req.param('state'); // Next? plugin.step.callback.next({ req: req, res: res }, args, function (response) { if (response) { return (!data.done.callback || data.done.callback == "oob") ? res.json(response) : res.redirect(data.done.callback + '?' + query.stringify(response)); } if ((step + 1) > plugin.steps) { return res.json(500, { code: 'ALL_STEPS_COMPLETE', message: 'All steps have been completed, authentication should have happened, please try again.' }); } res.redirect('/step/' + (step + 1)); }); }); app.use(function(req, res, next){ res.status(404); // respond with html page if (req.accepts('html')) { return res.send( '<html><head><title>Guardian.js</title></head><body>This route doesn\'t exist. ' + 'Please read the <a href=\'https://github.com/Mashape/guardian\'>documentation</a> ' + 'to see the available routes.</body></html>' ); } // respond with json if (req.accepts('json')) { return res.send({ error: 'Not found' }); } // default to plain-text. send() res.type('txt').send('Not found'); }); app.listen(config.port); log.info(('Worker #' + cluster.worker.id + ' on duty!').grey); } // Listen for dying workers cluster.on('exit', function (worker) { log.warn('STAB!'.red + (' Another worker has died :( RIP Worker #' + worker.id + '!').grey); cluster.fork(); }); });