diff --git a/index.js b/index.js index 7fe7b5d..523bc6e 100644 --- a/index.js +++ b/index.js @@ -9,27 +9,6 @@ var EventEmitter = require('events').EventEmitter, querystring = require('querystring'), serializer = require('serializer'); -_extend = function(dst,src) { - - var srcs = []; - if ( typeof(src) == 'object' ) { - srcs.push(src); - } else if ( typeof(src) == 'array' ) { - for (var i = src.length - 1; i >= 0; i--) { - srcs.push(this._extend({},src[i])) - }; - } else { - throw new Error("Invalid argument") - } - - for (var i = srcs.length - 1; i >= 0; i--) { - for (var key in srcs[i]) { - dst[key] = srcs[i][key]; - } - }; - - return dst; -} function parse_authorization(authorization) { if(!authorization) return null; @@ -70,23 +49,23 @@ function OAuth2Provider(options) { OAuth2Provider.prototype = new EventEmitter(); -OAuth2Provider.prototype.generateAccessToken = function(user_id, client_id, extra_data, token_options) { - token_options = token_options || {} - var out = _extend(token_options, { - access_token: this.serializer.stringify([user_id, client_id, +new Date, extra_data]), +OAuth2Provider.prototype.generateAccessToken = function(user_id, client_id, scope) { + var out = { + access_token: this.serializer.stringify([user_id, client_id, +new Date, scope]), refresh_token: null, - }); + }; + return out; }; OAuth2Provider.prototype.login = function() { - var self = this; + var __self = this; return function(req, res, next) { - var data, atok, user_id, client_id, grant_date, extra_data; + var data, atok, user_id, client_id, grant_date, scope; - if(req.query['access_token']) { - atok = req.query['access_token']; + if(req.query['access_token'] || req.body['access_token']) { + atok = req.query['access_token'] || req.body['access_token']; } else if((req.headers['authorization'] || '').indexOf('Bearer ') == 0) { atok = req.headers['authorization'].replace('Bearer', '').trim(); } else { @@ -94,20 +73,20 @@ OAuth2Provider.prototype.login = function() { } try { - data = self.serializer.parse(atok); + data = __self.serializer.parse(atok); user_id = data[0]; client_id = data[1]; grant_date = new Date(data[2]); - extra_data = data[3]; + scope = data[3]; } catch(e) { res.writeHead(400); return res.end(e.message); } - self.emit('access_token', req, { + __self.emit('access_token', req, { user_id: user_id, client_id: client_id, - extra_data: extra_data, + scope: scope, grant_date: grant_date }, next); }; @@ -121,9 +100,10 @@ OAuth2Provider.prototype.oauth = function() { if(req.method == 'GET' && self.options.authorize_uri == uri) { var client_id = req.query.client_id, - redirect_uri = req.query.redirect_uri; - - if(!client_id || !redirect_uri) { + redirect_uri = req.query.redirect_uri, + scope = (req.query.scope || "basic"); + + if(!client_id || !redirect_uri || !scope) { res.writeHead(400); return res.end('client_id and redirect_uri required'); } @@ -131,86 +111,106 @@ OAuth2Provider.prototype.oauth = function() { // authorization form will be POSTed to same URL, so we'll have all params var authorize_url = req.url; - self.emit('enforce_login', req, res, authorize_url, function(user_id) { + self.emit('enforce_login', req, res, authorize_url, scope, function(user_id) { // store user_id in an HMAC-protected encrypted query param authorize_url += '&' + querystring.stringify({x_user_id: self.serializer.stringify(user_id)}); - - // user is logged in, render approval page - self.emit('authorize_form', req, res, client_id, authorize_url); + + //fires when use allows app + self.emit('before_authorize_form', req, res, client_id, scope, function() { + self.emit('authorize_form', req, res, client_id, authorize_url, scope); + },function(){ + var code = serializer.randomString(128); + self.emit('save_grant', req, client_id, code, scope, function() { + var extras = { + code: code, + }; + var url = redirect_uri; + url += '?'; + // pass back anti-CSRF opaque value + if (state) extras['state'] = state; + url += querystring.stringify(extras); + res.writeHead(303, { + Location: url + }); + res.end(); + }); + }); }); } else if(req.method == 'POST' && self.options.authorize_uri == uri) { - var client_id = (req.query.client_id || req.body.client_id), - redirect_uri = (req.query.redirect_uri || req.body.redirect_uri), - response_type = (req.query.response_type || req.body.response_type) || 'code', - state = (req.query.state || req.body.state), - x_user_id = (req.query.x_user_id || req.body.x_user_id); - - var url = redirect_uri; - - switch(response_type) { - case 'code': url += '?'; break; - case 'token': url += '#'; break; + //fires when use allows app + var client_id = (req.query.client_id || req.body.client_id), + scope = (req.query.scope || req.body.scope || "basic"), + redirect_uri = (req.query.redirect_uri || req.body.redirect_uri), + response_type = (req.query.response_type || req.body.response_type) || 'code', + state = (req.query.state || req.body.state), + x_user_id = (req.query.x_user_id || req.body.x_user_id); + var url = redirect_uri; + switch (response_type) { + case 'code': + url += '?'; + break; + case 'token': + url += '#'; + break; default: - res.writeHead(400); - return res.end('invalid response_type requested'); - } - - if('allow' in req.body) { - if('token' == response_type) { - var user_id; - - try { - user_id = self.serializer.parse(x_user_id); - } catch(e) { - console.error('allow/token error', e.stack); - - res.writeHead(500); - return res.end(e.message); - } - - self.emit('create_access_token', user_id, client_id, function(extra_data,token_options) { - var atok = self.generateAccessToken(user_id, client_id, extra_data, token_options); - - if(self.listeners('save_access_token').length > 0) - self.emit('save_access_token', user_id, client_id, atok); - - url += querystring.stringify(atok); - - res.writeHead(303, {Location: url}); - res.end(); - }); - } else { - var code = serializer.randomString(128); - - self.emit('save_grant', req, client_id, code, function() { - var extras = { - code: code, - }; - - // pass back anti-CSRF opaque value - if(state) - extras['state'] = state; - - url += querystring.stringify(extras); - - res.writeHead(303, {Location: url}); + res.writeHead(400); + return res.end('invalid response_type requested'); + } + if ('allow' in req.body) { + self.emit('after_authorize_form', req, res, client_id, scope,function(){ + if ('token' == response_type) { + var user_id; + try { + user_id = self.serializer.parse(x_user_id); + } + catch (e) { + console.error('allow/token error', e.stack); + res.writeHead(500); + return res.end(e.message); + } + //self.emit('create_access_token', req, user_id, client_id, scope, function(scope) { + var atok = self.generateAccessToken(user_id, client_id, scope); + if (self.listeners('save_access_token').length > 0) self.emit('save_access_token', user_id, client_id, atok); + url += querystring.stringify(atok); + res.writeHead(303, { + Location: url + }); + res.end(); + //}); + } + else { + var code = serializer.randomString(128); + self.emit('save_grant', req, client_id, code, scope, function() { + var extras = { + code: code, + }; + // pass back anti-CSRF opaque value + if (state) extras['state'] = state; + url += querystring.stringify(extras); + res.writeHead(303, { + Location: url + }); + res.end(); + }); + } + }); + } + else { + url += querystring.stringify({ + error: 'access_denied' + }); + res.writeHead(303, { + Location: url + }); res.end(); - }); } - } else { - url += querystring.stringify({error: 'access_denied'}); - - res.writeHead(303, {Location: url}); - res.end(); - } - } else if(req.method == 'POST' && self.options.access_token_uri == uri) { - var client_id = req.body.client_id, - client_secret = req.body.client_secret, - redirect_uri = req.body.redirect_uri, - code = req.body.code; - + var client_id = req.body.client_id || req.query.client_id, + redirect_uri = req.body.redirect_uri || req.query.redirect_uri, + client_secret = req.body.client_secret || req.query.client_secret, + code = req.body.code || req.query.code; + if(!client_id || !client_secret) { var authorization = parse_authorization(req.headers.authorization); @@ -222,14 +222,15 @@ OAuth2Provider.prototype.oauth = function() { client_id = authorization[0]; client_secret = authorization[1]; } - + + //post auth if('password' == req.body.grant_type) { if(self.listeners('client_auth').length == 0) { res.writeHead(401); return res.end('client authentication not supported'); } - self.emit('client_auth', client_id, client_secret, req.body.username, req.body.password, function(err, user_id) { + self.emit('client_auth', client_id, client_secret, req.body.username, req.body.password, function(err, user_id, scope) { if(err) { res.writeHead(401); return res.end(err.message); @@ -237,12 +238,13 @@ OAuth2Provider.prototype.oauth = function() { res.writeHead(200, {'Content-type': 'application/json'}); - self._createAccessToken(user_id, client_id, function(atok) { + self._createAccessToken(scope, user_id, client_id, function(atok) { + self.emit('remove_grant', user_id, client_id, code, scope); res.end(JSON.stringify(atok)); }); }); } else { - self.emit('lookup_grant', client_id, client_secret, code, function(err, user_id) { + self.emit('lookup_grant', client_id, client_secret, code, function(err, user_id, scope) { if(err) { res.writeHead(400); return res.end(err.message); @@ -250,8 +252,8 @@ OAuth2Provider.prototype.oauth = function() { res.writeHead(200, {'Content-type': 'application/json'}); - self._createAccessToken(user_id, client_id, function(atok) { - self.emit('remove_grant', user_id, client_id, code); + self._createAccessToken(scope, user_id, client_id, function(atok) { + self.emit('remove_grant', user_id, client_id, code, scope); res.end(JSON.stringify(atok)); }); @@ -264,17 +266,17 @@ OAuth2Provider.prototype.oauth = function() { }; }; -OAuth2Provider.prototype._createAccessToken = function(user_id, client_id, cb) { +OAuth2Provider.prototype._createAccessToken = function(scope, user_id, client_id, cb) { var self = this; - this.emit('create_access_token', user_id, client_id, function(extra_data, token_options) { - var atok = self.generateAccessToken(user_id, client_id, extra_data, token_options); + //this.emit('create_access_token', req, user_id, client_id, function(scope) { + var atok = self.generateAccessToken(user_id, client_id, scope); if(self.listeners('save_access_token').length > 0) - self.emit('save_access_token', user_id, client_id, atok); + self.emit('save_access_token', user_id, client_id, atok, scope); return cb(atok); - }); + //}); }; exports.OAuth2Provider = OAuth2Provider;