UNPKG

cas-secure

Version:

Secure Express/Connect API's against a CAS Server

129 lines (128 loc) 5.98 kB
var http = require('http'); var https = require('https'); const { URL } = require('url'); var qs = require('querystring'); var parseXML = require('xml2js').parseString; var XMLprocessors = require('xml2js/lib/processors'); var debug = require('debug')('cas-secure'); module.exports = { set: function(options) { if(typeof options == 'string') options = {base_url: options}; if(!options.base_url) throw new Error('No CAS Base URL provided'); if(!options.version||['1','2','3'].indexOf(options.version+'')==-1) options.version = '3'; if(!options.action||['block', 'pass', 'ignore'].indexOf(options.action)==-1) options.action = 'block'; switch(options.version) { case '1': options.validateUrl = options.validateUrl||'/validate'; options.validate = function(body, callback) { var lines = body.split('\n'); if (lines[ 0 ] === 'yes' && lines.length >= 2) { return callback(null, {user: lines[ 1 ]}); } else if (lines[ 0 ] === 'no') { return callback( new Error('CAS authentication failed.')); } else { return callback( new Error('Response from CAS server was bad.')); } } break; case '2': options.validateUrl = options.validateUrl||'/proxyValidate'; default: options.validateUrl = options.validateUrl||'/p3/proxyValidate'; options.validate = function(body, callback) { parseXML(body, { trim: true, normalize: true, explicitArray: false, tagNameProcessors: [ XMLprocessors.normalize, XMLprocessors.stripPrefix ] }, function(err, result) { if (err) { return callback(new Error('Response from CAS server was bad.')); } try { var failure = result.serviceresponse.authenticationfailure; if (failure) { return callback(new Error('CAS authentication failed (' + failure.$.code + ').')); } var success = result.serviceresponse.authenticationsuccess; if (success) { return callback(null, {user: success.user, attributes: success.attributes}); } else { return callback(new Error( 'CAS authentication failed.')); } } catch (error) { debug("exception stacktrace: "); debug(error); return callback(new Error('CAS authentication failed.')); } }); }; } this.options = options; this.parsed = new URL(options.base_url+options.validateUrl); this.client = this.parsed.protocol == 'https:'?https:http; return this; }, /** * When encountered with an error, actions can be * 'block': write response with errors * 'pass': pass error to next function * 'ignore': just call next without writing req.cas */ validate: function(action) { if(!action||['block', 'pass', 'ignore'].indexOf(action)==-1) action=this.options.action; var self = this; return function(req, res, next) { var doExit = function(status, message) { debug('Status: '+status+' '+message); if(action == 'block') { res.writeHead(status); return res.end(message); } var error = new Error(message); error.statusCode = status; next(action=='pass'?error:null); } var ticket = req.query&&req.query.ticket; if(!ticket && req.headers.authorization) { var split = req.headers.authorization.split(' '); if(split[0]=='Bearer') ticket = split[1]; } if(!ticket) { debug('no ticket found'); return doExit(401, 'Unauthorized. No service ticket found or invalid.'); } var service = req.query&&req.query.service; self.parsed.search = qs.stringify({ticket: ticket, service: self.options.service||req.headers.host}); debug('Sending request to: '+self.parsed.href); var request = self.client.request(self.parsed.href, function(response) { var body=''; response.on('data', function(chunk) { body += chunk; }); response.on('error', function(error) { doExit(500, 'Error on validation response: '+error.message); }); response.on('end', function() { debug('Response body: '+body); self.options.validate(body, function(error, data) { if(error) { debug('Error on validation: '+error.message); return doExit(401, 'Unauthorized. No service ticket found or invalid.'); } req.cas = data; process.nextTick(next); }); }); }); request.on('error', function(error){ doExit(500, 'Error on validation request: '+error.message); }); request.end(); } } };