webservices/node_modules/oauth2orize/lib/middleware/authorization.js

243 lines
10 KiB
JavaScript

/**
* Module dependencies.
*/
var utils = require('../utils')
, AuthorizationError = require('../errors/authorizationerror');
/**
* Handle authorization requests from OAuth 2.0 clients.
*
* Obtaining authorization via OAuth 2.0 consists of a sequence of discrete
* steps. First, the client requests authorization from the user (in this case
* using an authorization server as an intermediary). The authorization server
* conducts an approval dialog with the user to obtain permission. After access
* has been allowed, a grant is issued to the client which can be exchanged for
* an access token.
*
* This middleware is used to initiate authorization transactions. If a request
* is parsed and validated, the following properties will be available on the
* request:
*
* req.oauth2.transactionID an ID assigned to this transaction
* req.oauth2.client client requesting the user's authorization
* req.oauth2.redirectURI URL to redirect the user to after authorization
* req.oauth2.req parameters from request made by the client
*
* The contents of `req.oauth2.req` depends on the grant type requested by the
* the client. The `server`'s request parsing functions are used to construct
* this object, and the application can implement support for these types as
* necessary, taking advantage of bundled grant middleware.
*
* Because the approval dialog may be conducted over a series of requests and
* responses, a transaction is also stored in the session until a decision is
* reached. The application is responsible for verifying the user's identity
* and prompting him or her to allow or deny the request (typically via an HTML
* form). At that point, `decision` middleware can be utilized to process the
* user's decision and issue the grant to the client.
*
* Callbacks:
*
* This middleware requires a `validate` callback, for which the function
* signature is as follows:
*
* function(clientID, redirectURI, done) { ... }
*
* `clientID` is the client identifier and `redirectURI` is the redirect URI as
* indicated by the client. If the request is valid, `done` must be invoked
* with the following signature:
*
* done(err, client, redirectURI);
*
* `client` is the client instance which is making the request. `redirectURI`
* is the URL to which the user will be redirected after authorization is
* obtained (which may be different, if the server is enforcing registration
* requirements). If an error occurs, `done` should be invoked with `err` set
* in idomatic Node.js fashion.
*
* Alternate function signatures of the `validate` callback are available if
* needed. Consult the source code for a definitive reference.
*
*
* Note that authorization may be obtained by the client directly from the user
* without using an authorization server as an intermediary (for example, when
* obtaining a grant in the form of the user's password credentials). In these
* cases, the client interacts only with the token endpoint without any need to
* interact with the authorization endpoint.
*
* Options:
*
* idLength length of generated transaction IDs (default: 8)
* sessionKey key under which transactions are stored in the session (default: 'authorize')
*
* Examples:
*
* app.get('/dialog/authorize',
* login.ensureLoggedIn(),
* server.authorization(function(clientID, redirectURI, done) {
* Clients.findOne(clientID, function(err, client) {
* if (err) { return done(err); }
* if (!client) { return done(null, false); }
* return done(null, client, client.redirectURI);
* });
* }),
* function(req, res) {
* res.render('dialog', { transactionID: req.oauth2.transactionID,
* user: req.user, client: req.oauth2.client });
* });
*
* References:
* - [Authorization Endpoint](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.1)
*
* @param {Server} server
* @param {Object} options
* @param {Function} validate
* @return {Function}
* @api protected
*/
module.exports = function(server, options, validate, immediate, complete) {
if (typeof options == 'function') {
complete = immediate;
immediate = validate;
validate = options;
options = undefined;
}
options = options || {};
immediate = immediate || function (client, user, done) { return done(null, false); };
if (!server) { throw new TypeError('oauth2orize.authorization middleware requires a server argument'); }
if (!validate) { throw new TypeError('oauth2orize.authorization middleware requires a validate function'); }
var userProperty = options.userProperty || 'user';
return function authorization(req, res, next) {
var body = req.body || {}
, type = req.query.response_type || body.response_type;
server._parse(type, req, function(err, areq) {
if (err) { return next(err); }
if (!areq || !areq.type) { return next(new AuthorizationError('Missing required parameter: response_type', 'invalid_request')); }
if (areq.type && !areq.clientID) { return next(new AuthorizationError('Unsupported response type: ' + type, 'unsupported_response_type')); }
function validated(err, client, redirectURI, webOrigin) {
// Set properties *before* next()'ing due to error. The presence of a
// redirectURI being provided, even under error conditions, indicates
// that the client should be informed of the error via a redirect.
req.oauth2 = {};
if (client) { req.oauth2.client = client; }
if (redirectURI) { req.oauth2.redirectURI = redirectURI; }
if (webOrigin) { req.oauth2.webOrigin = webOrigin; }
req.oauth2.req = areq;
req.oauth2.user = req[userProperty];
if (req.locals) { req.oauth2.locals = req.locals; }
if (err) { return next(err); }
if (!client) { return next(new AuthorizationError('Unauthorized client', 'unauthorized_client')); }
function immediated(err, allow, info, locals) {
if (err) { return next(err); }
if (allow) {
req.oauth2.res = info || {};
req.oauth2.res.allow = true;
if (locals) {
req.oauth2.locals = req.oauth2.locals || {};
utils.merge(req.oauth2.locals, locals);
}
function completing(cb) {
if (!complete) { return cb(); }
complete(req, req.oauth2, cb);
}
server._respond(req.oauth2, res, completing, function(err) {
if (err) { return next(err); }
return next(new AuthorizationError('Unsupported response type: ' + req.oauth2.req.type, 'unsupported_response_type'));
});
} else {
// Add info and locals to `req.oauth2`, where they will be
// available to the next middleware. Since this is a
// non-immediate response, the next middleware's responsibility is
// to prompt the user to allow or deny access. `info` and
// `locals` are passed along as they may be of assistance when
// rendering the prompt.
//
// Note that, when using the legacy transaction store, `info` is
// also serialized into the transaction, where it can further be
// utilized in the `decision` middleware after the user submits the
// prompt's form. As such, `info` should be a normal JSON object,
// so that it can be correctly serialized into the session.
// `locals` is only carried through to the middleware chain for the
// current request, so it may contain instantiated classes that
// don't serialize cleanly.
//
// The transaction store is pluggable when initializing the `Server`
// instance. If an application implements a custom transaction
// store, the specific details of what properties are serialized
// into the transaction and loaded on subsequent requests are
// determined by the implementation.
req.oauth2.info = info;
if (locals) {
req.oauth2.locals = req.oauth2.locals || {};
utils.merge(req.oauth2.locals, locals);
}
// A dialog needs to be conducted to obtain the user's approval.
// Serialize a transaction to the session. The transaction will be
// restored (and removed) from the session when the user allows or
// denies the request.
function stored(err, tid) {
if (err) { return next(err); }
req.oauth2.transactionID = tid;
next();
}
if (server._txnStore.legacy == true) {
var txn = {};
txn.protocol = 'oauth2';
txn.client = client;
txn.redirectURI = redirectURI;
txn.webOrigin = webOrigin;
txn.req = areq;
txn.info = info;
server._txnStore.store(server, options, req, txn, stored);
} else {
server._txnStore.store(req, req.oauth2, stored);
}
}
}
var arity = immediate.length;
if (arity == 7) {
immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, req.oauth2.req.type, req.oauth2.req, req.oauth2.locals, immediated);
} else if (arity == 6) {
immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, req.oauth2.req.type, req.oauth2.req, immediated);
} else if (arity == 5) {
immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, req.oauth2.req.type, immediated);
} else if (arity == 4) {
immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, immediated);
} else if (arity == 3) {
immediate(req.oauth2.client, req.oauth2.user, immediated);
} else { // arity == 2
immediate(req.oauth2, immediated);
}
}
try {
var arity = validate.length;
if (arity == 5) {
validate(areq.clientID, areq.redirectURI, areq.scope, areq.type, validated);
} else if (arity == 4) {
validate(areq.clientID, areq.redirectURI, areq.scope, validated);
} else if (arity == 3) {
validate(areq.clientID, areq.redirectURI, validated);
} else { // arity == 2
validate(areq, validated);
}
} catch (ex) {
return next(ex);
}
});
};
};