Merge pull request #2108 from benkroeger/hotfix/issue-2106

Allow built-in token middleware to run repeatedly
This commit is contained in:
Miroslav Bajtoš 2016-04-06 15:45:21 +02:00
commit a9c7801380
2 changed files with 103 additions and 3 deletions

View File

@ -62,6 +62,8 @@ function escapeRegExp(str) {
* @property {Array} [headers] Array of header names. * @property {Array} [headers] Array of header names.
* @property {Array} [params] Array of param names. * @property {Array} [params] Array of param names.
* @property {Boolean} [searchDefaultTokenKeys] Use the default search locations for Token in request * @property {Boolean} [searchDefaultTokenKeys] Use the default search locations for Token in request
* @property {Boolean} [enableDoublecheck] Execute middleware although an instance mounted earlier in the chain didn't find a token
* @property {Boolean} [overwriteExistingToken] only has effect in combination with `enableDoublecheck`. If truthy, will allow to overwrite an existing accessToken.
* @property {Function|String} [model] AccessToken model name or class to use. * @property {Function|String} [model] AccessToken model name or class to use.
* @property {String} [currentUserLiteral] String literal for the current user. * @property {String} [currentUserLiteral] String literal for the current user.
* @header loopback.token([options]) * @header loopback.token([options])
@ -80,6 +82,9 @@ function token(options) {
currentUserLiteral = escapeRegExp(currentUserLiteral); currentUserLiteral = escapeRegExp(currentUserLiteral);
} }
var enableDoublecheck = !!options.enableDoublecheck;
var overwriteExistingToken = !!options.overwriteExistingToken;
return function(req, res, next) { return function(req, res, next) {
var app = req.app; var app = req.app;
var registry = app.registry; var registry = app.registry;
@ -97,8 +102,19 @@ function token(options) {
'loopback.token() middleware requires a AccessToken model'); 'loopback.token() middleware requires a AccessToken model');
if (req.accessToken !== undefined) { if (req.accessToken !== undefined) {
rewriteUserLiteral(req, currentUserLiteral); if (!enableDoublecheck) {
return next(); // req.accessToken is defined already (might also be "null" or "false") and enableDoublecheck
// has not been set --> skip searching for credentials
rewriteUserLiteral(req, currentUserLiteral);
return next();
}
if (req.accessToken && req.accessToken.id && !overwriteExistingToken) {
// req.accessToken.id is defined, which means that some other middleware has identified a valid user.
// when overwriteExistingToken is not set to a truthy value, skip searching for credentials.
rewriteUserLiteral(req, currentUserLiteral);
return next();
}
// continue normal operation (as if req.accessToken was undefined)
} }
TokenModel.findForRequest(req, options, function(err, token) { TokenModel.findForRequest(req, options, function(err, token) {
req.accessToken = token || null; req.accessToken = token || null;

View File

@ -65,7 +65,7 @@ describe('loopback.token(options)', function() {
.end(done); .end(done);
}); });
describe('populating req.toen from HTTP Basic Auth formatted authorization header', function() { describe('populating req.token from HTTP Basic Auth formatted authorization header', function() {
it('parses "standalone-token"', function(done) { it('parses "standalone-token"', function(done) {
var token = this.token.id; var token = this.token.id;
token = 'Basic ' + new Buffer(token).toString('base64'); token = 'Basic ' + new Buffer(token).toString('base64');
@ -199,6 +199,90 @@ describe('loopback.token(options)', function() {
done(); done();
}); });
}); });
describe('loading multiple instances of token middleware', function() {
it('should skip when req.token is already present and no further options are set',
function(done) {
var tokenStub = { id: 'stub id' };
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({ model: Token }));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', this.token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql(tokenStub);
done();
});
});
it('should not overwrite valid existing token (has "id" property) ' +
' when overwriteExistingToken is falsy',
function(done) {
var tokenStub = { id: 'stub id' };
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({
model: Token,
enableDoublecheck: true,
}));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', this.token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql(tokenStub);
done();
});
});
it('should overwrite existing token when enableDoublecheck ' +
'and overwriteExistingToken options are truthy',
function(done) {
var token = this.token;
var tokenStub = { id: 'stub id' };
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({
model: Token,
enableDoublecheck: true,
overwriteExistingToken: true,
}));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql({
id: token.id,
ttl: token.ttl,
userId: token.userId,
created: token.created.toJSON(),
});
done();
});
});
});
}); });
describe('AccessToken', function() { describe('AccessToken', function() {