/** * 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); } }); }; };