webservices/node_modules/oauth2orize/lib/server.js

435 lines
11 KiB
JavaScript

/**
* Module dependencies.
*/
var SessionStore = require('./txn/session')
, UnorderedList = require('./unorderedlist')
, authorization = require('./middleware/authorization')
, resume = require('./middleware/resume')
, decision = require('./middleware/decision')
, transactionLoader = require('./middleware/transactionLoader')
, token = require('./middleware/token')
, authorizationErrorHandler = require('./middleware/authorizationErrorHandler')
, errorHandler = require('./middleware/errorHandler')
, utils = require('./utils')
, debug = require('debug')('oauth2orize');
/**
* `Server` constructor.
*
* @api public
*/
function Server(options) {
options = options || {};
this._reqParsers = [];
this._resHandlers = [];
this._errHandlers = [];
this._exchanges = [];
this._serializers = [];
this._deserializers = [];
this._txnStore = options.store || new SessionStore();
}
/**
* Register authorization grant middleware.
*
* OAuth 2.0 defines an authorization framework, in which authorization grants
* can be of a variety of types. Initiating and responding to an OAuth 2.0
* authorization transaction is implemented by grant middleware, and the server
* registers the middleware it wishes to support.
*
* Examples:
*
* server.grant(oauth2orize.grant.code());
*
* server.grant('*', function(req) {
* return { host: req.headers['host'] }
* });
*
* server.grant('foo', function(req) {
* return { foo: req.query['foo'] }
* });
*
* @param {String|Object} type
* @param {String} phase
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
Server.prototype.grant = function(type, phase, fn) {
if (typeof type == 'object') {
// sig: grant(mod)
var mod = type;
if (mod.request) { this.grant(mod.name, 'request', mod.request); }
if (mod.response) { this.grant(mod.name, 'response', mod.response); }
if (mod.error) { this.grant(mod.name, 'error', mod.error); }
return this;
}
if (typeof phase == 'object') {
// sig: grant(type, mod)
var mod = phase;
if (mod.request) { this.grant(type, 'request', mod.request); }
if (mod.response) { this.grant(type, 'response', mod.response); }
if (mod.error) { this.grant(type, 'error', mod.error); }
return this;
}
if (typeof phase == 'function') {
// sig: grant(type, fn)
fn = phase;
phase = 'request';
}
if (type === '*') { type = null; }
if (type) { type = new UnorderedList(type); }
if (phase == 'request') {
debug('register request parser %s %s', type || '*', fn.name || 'anonymous');
this._reqParsers.push({ type: type, handle: fn });
} else if (phase == 'response') {
debug('register response handler %s %s', type || '*', fn.name || 'anonymous');
this._resHandlers.push({ type: type, handle: fn });
} else if (phase == 'error') {
debug('register error handler %s %s', type || '*', fn.name || 'anonymous');
this._errHandlers.push({ type: type, handle: fn });
}
return this;
};
/**
* Register token exchange middleware.
*
* OAuth 2.0 defines an authorization framework, in which authorization grants
* can be of a variety of types. Exchanging of these types for access tokens is
* implemented by exchange middleware, and the server registers the middleware
* it wishes to support.
*
* Examples:
*
* server.exchange(oauth2orize.exchange.authorizationCode(function() {
* ...
* }));
*
* @param {String|Function} type
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
Server.prototype.exchange = function(type, fn) {
if (typeof type == 'function') {
fn = type;
type = fn.name;
}
if (type === '*') { type = null; }
debug('register exchanger %s %s', type || '*', fn.name || 'anonymous');
this._exchanges.push({ type: type, handle: fn });
return this;
};
/**
* Parses requests to obtain authorization.
*
* @api public
*/
Server.prototype.authorize =
Server.prototype.authorization = function(options, validate, immediate, complete) {
return authorization(this, options, validate, immediate, complete);
};
Server.prototype.resume = function(options, immediate, complete) {
var loader;
if (typeof options == 'function' && typeof immediate == 'function' && typeof complete == 'function') {
options = { loadTransaction: options };
}
if (options && options.loadTransaction === false) {
return resume(this, options, immediate, complete);
}
if (options && typeof options.loadTransaction === 'function') {
loader = options.loadTransaction;
} else {
loader = transactionLoader(this, options);
}
return [loader, resume(this, options, immediate, complete)];
};
/**
* Handle a user's response to an authorization dialog.
*
* @api public
*/
Server.prototype.decision = function(options, parse, complete) {
if (options && options.loadTransaction === false) {
return decision(this, options, parse, complete);
}
return [transactionLoader(this, options), decision(this, options, parse, complete)];
};
Server.prototype.authorizeError =
Server.prototype.authorizationError =
Server.prototype.authorizationErrorHandler = function(options) {
var loader = transactionLoader(this, options);
return [
function transactionLoaderErrorWrapper(err, req, res, next) {
loader(req, res, function(ierr) {
return next(err);
});
},
authorizationErrorHandler(this, options)
];
};
/**
* Handle requests to exchange an authorization grant for an access token.
*
* @api public
*/
Server.prototype.token = function(options) {
return token(this, options);
};
/**
* Respond to errors encountered in OAuth 2.0 endpoints.
*
* @api public
*/
Server.prototype.errorHandler = function(options) {
return errorHandler(options);
};
/**
* Registers a function used to serialize client objects into the session.
*
* Examples:
*
* server.serializeClient(function(client, done) {
* done(null, client.id);
* });
*
* @api public
*/
Server.prototype.serializeClient = function(fn, done) {
if (typeof fn === 'function') {
return this._serializers.push(fn);
}
// private implementation that traverses the chain of serializers, attempting
// to serialize a client
var client = fn;
var stack = this._serializers;
(function pass(i, err, obj) {
// serializers use 'pass' as an error to skip processing
if ('pass' === err) { err = undefined; }
// an error or serialized object was obtained, done
if (err || obj) { return done(err, obj); }
var layer = stack[i];
if (!layer) {
return done(new Error('Failed to serialize client. Register serialization function using serializeClient().'));
}
try {
layer(client, function(e, o) { pass(i + 1, e, o); } );
} catch (ex) {
return done(ex);
}
})(0);
};
/**
* Registers a function used to deserialize client objects out of the session.
*
* Examples:
*
* server.deserializeClient(function(id, done) {
* Client.findById(id, function (err, client) {
* done(err, client);
* });
* });
*
* @api public
*/
Server.prototype.deserializeClient = function(fn, done) {
if (typeof fn === 'function') {
return this._deserializers.push(fn);
}
// private implementation that traverses the chain of deserializers,
// attempting to deserialize a client
var obj = fn;
var stack = this._deserializers;
(function pass(i, err, client) {
// deserializers use 'pass' as an error to skip processing
if ('pass' === err) { err = undefined; }
// an error or deserialized client was obtained, done
if (err || client) { return done(err, client); }
// a valid client existed when establishing the session, but that client has
// since been deauthorized
if (client === null || client === false) { return done(null, false); }
var layer = stack[i];
if (!layer) {
return done(new Error('Failed to deserialize client. Register deserialization function using deserializeClient().'));
}
try {
layer(obj, function(e, c) { pass(i + 1, e, c); } );
} catch (ex) {
return done(ex);
}
})(0);
};
/**
* Parse authorization request into transaction using registered grant middleware.
*
* @param {String} type
* @param {http.ServerRequest} req
* @param {Function} cb
* @api private
*/
Server.prototype._parse = function(type, req, cb) {
var ultype = new UnorderedList(type)
, stack = this._reqParsers
, areq = {};
if (type) { areq.type = type; }
(function pass(i) {
var layer = stack[i];
if (!layer) { return cb(null, areq); }
try {
debug('parse:%s', layer.handle.name || 'anonymous');
if (layer.type === null || layer.type.equalTo(ultype)) {
var arity = layer.handle.length;
if (arity == 1) { // sync
var o = layer.handle(req);
utils.merge(areq, o);
pass(i + 1);
} else { // async
layer.handle(req, function(err, o) {
if (err) { return cb(err); }
utils.merge(areq, o);
pass(i + 1);
});
}
} else {
pass(i + 1);
}
} catch (ex) {
return cb(ex);
}
})(0);
};
/**
* Respond to authorization transaction using registered grant middleware.
*
* @param {Object} txn
* @param {http.ServerResponse} res
* @param {Function} cb
* @api private
*/
Server.prototype._respond = function(txn, res, complete, cb) {
if (cb === undefined) {
cb = complete;
complete = undefined;
}
complete = complete || function(cb) { cb(); }
var ultype = new UnorderedList(txn.req.type)
, stack = this._resHandlers
, idx = 0;
function next(err) {
if (err) { return cb(err); }
var layer = stack[idx++];
if (!layer) { return cb(); }
try {
debug('respond:%s', layer.handle.name || 'anonymous');
if (layer.type === null || layer.type.equalTo(ultype)) {
var arity = layer.handle.length;
if (arity == 4) {
layer.handle(txn, res, complete, next);
} else {
layer.handle(txn, res, next);
}
} else {
next();
}
} catch (ex) {
return cb(ex);
}
}
next();
};
Server.prototype._respondError = function(err, txn, res, cb) {
var ultype = new UnorderedList(txn.req.type)
, stack = this._errHandlers
, idx = 0;
function next(err) {
var layer = stack[idx++];
if (!layer) { return cb(err); }
try {
debug('error:%s', layer.handle.name || 'anonymous');
if (layer.type === null || layer.type.equalTo(ultype)) {
layer.handle(err, txn, res, next);
} else {
next(err);
}
} catch (ex) {
return cb(ex);
}
}
next(err);
}
/**
* Process token request using registered exchange middleware.
*
* @param {String} type
* @param {http.ServerRequest} req
* @param {http.ServerResponse} res
* @param {Function} cb
* @api private
*/
Server.prototype._exchange = function(type, req, res, cb) {
var stack = this._exchanges
, idx = 0;
function next(err) {
if (err) { return cb(err); }
var layer = stack[idx++];
if (!layer) { return cb(); }
try {
debug('exchange:%s', layer.handle.name || 'anonymous');
if (layer.type === null || layer.type === type) {
layer.handle(req, res, next);
} else {
next();
}
} catch (ex) {
return cb(ex);
}
}
next();
};
/**
* Expose `Server`.
*/
exports = module.exports = Server;