Add loopback.token() middleware

This commit is contained in:
Ritchie Martori 2013-11-14 13:01:47 -08:00
parent 77a137eca6
commit 64d8ff986b
5 changed files with 89 additions and 17 deletions

View File

@ -11,18 +11,22 @@ var RemoteObjects = require('strong-remoting');
module.exports = token; module.exports = token;
/**
*
*/
function token(app, options) { function token(app, options) {
options = options || {}; options = options || {};
var tokenModelName = options.tokenModelName || 'Token'; var tokenModelName = options.tokenModelName || 'AccessToken';
var TokenModel = app.getModel(tokenModelName); var TokenModel = options.model;
var tokenHeaderName = options.tokenHeaderName || 'X-Access-Token'; assert(TokenModel, 'loopback.token() middleware requires a AccessToken model');
return function (req, res, next) { return function (req, res, next) {
next(); TokenModel.findForRequest(req, options, function(err, token) {
if(err) return next(err);
if(token) {
req.accessToken = token;
next();
} else {
return next();
}
});
} }
} }

View File

@ -4,7 +4,8 @@
var Model = require('../loopback').Model var Model = require('../loopback').Model
, loopback = require('../loopback') , loopback = require('../loopback')
, crypto = require('crypto'); , crypto = require('crypto')
, uid = require('uid2');
/** /**
* Default AccessToken properties. * Default AccessToken properties.
@ -20,7 +21,7 @@ var properties = {
* Extends from the built in `loopback.Model` type. * Extends from the built in `loopback.Model` type.
*/ */
var AccessToken = module.exports = Model.extend('access-token', properties); var AccessToken = module.exports = Model.extend('AccessToken', properties);
/** /**
* Create a cryptographically random access token id. * Create a cryptographically random access token id.
@ -29,7 +30,7 @@ var AccessToken = module.exports = Model.extend('access-token', properties);
*/ */
AccessToken.createAccessTokenId = function (fn) { AccessToken.createAccessTokenId = function (fn) {
crypto.randomBytes(this.settings.accessTokenIdLength || 64, function(err, buf) { uid(this.settings.accessTokenIdLength || 64, function(err, buf) {
if(err) { if(err) {
fn(err); fn(err);
} else { } else {
@ -54,3 +55,64 @@ AccessToken.beforeCreate = function (next, data) {
} }
}); });
} }
/**
* Find a token for the given `ServerRequest`.
*
* @param {ServerRequest} req
* @param {Object} [options] Options for finding the token
* @param {Function} callback Calls back with a token if one exists otherwise null or an error.
*/
AccessToken.findForRequest = function(req, options, cb) {
var id = tokenIdForRequest(req, options);
if(id) {
this.findById(id, cb);
} else {
process.nextTick(function() {
cb();
});
}
}
function tokenIdForRequest(req, options) {
var params = options.params || [];
var headers = options.headers || [];
var cookies = options.cookies || [];
var i = 0;
var length;
var id;
params.push('access_token');
headers.push('X-Access-Token');
headers.push('authorization');
cookies.push('access_token');
cookies.push('authorization');
for(length = params.length; i < length; i++) {
id = req.param(params[i]);
if(typeof id === 'string') {
return id;
}
}
for(length = headers.length; i < length; i++) {
id = req.header(params[i]);
if(typeof id === 'string') {
return id;
}
}
for(length = headers.length; i < length; i++) {
id = req.signedCookies(cookies[i]);
if(typeof id === 'string') {
return id;
}
}
return null;
}

View File

@ -29,7 +29,8 @@
"ejs": "~0.8.4", "ejs": "~0.8.4",
"bcryptjs": "~0.7.10", "bcryptjs": "~0.7.10",
"underscore.string": "~2.3.3", "underscore.string": "~2.3.3",
"underscore": "~1.5.2" "underscore": "~1.5.2",
"uid2": "0.0.3"
}, },
"devDependencies": { "devDependencies": {
"blanket": "~1.1.5", "blanket": "~1.1.5",

View File

@ -10,12 +10,17 @@ describe('loopback.token(app, options)', function() {
it('should populate req.token from the query string', function (done) { it('should populate req.token from the query string', function (done) {
var app = loopback(); var app = loopback();
var options = {}; var options = {};
options.model = Token;
var testToken = this.token; var testToken = this.token;
app.use(loopback.token(app, options)); app.use(loopback.token(app, options));
app.get('/', function (req, res) { app.get('/', function (req, res) {
assert(req.token === testToken); try {
assert(req.accessToken, 'req should have accessToken');
assert(req.accessToken.id === testToken.id);
} catch(e) {
return done(e);
}
res.send('ok'); res.send('ok');
done();
}); });
request(app) request(app)
@ -32,4 +37,4 @@ function createTestingToken(done) {
test.token = token; test.token = token;
done(); done();
}); });
} }

View File

@ -103,7 +103,7 @@ describe('User', function(){
User.login({email: 'foo@bar.com', password: 'bar'}, function (err, accessToken) { User.login({email: 'foo@bar.com', password: 'bar'}, function (err, accessToken) {
assert(accessToken.uid); assert(accessToken.uid);
assert(accessToken.id); assert(accessToken.id);
assert.equal((new Buffer(accessToken.id, 'base64')).length, 64); assert.equal(accessToken.id.length, 64);
done(); done();
}); });
@ -121,7 +121,7 @@ describe('User', function(){
assert(accessToken.uid); assert(accessToken.uid);
assert(accessToken.id); assert(accessToken.id);
assert.equal((new Buffer(accessToken.id, 'base64')).length, 64); assert.equal(accessToken.id.length, 64);
done(); done();
}); });