Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions server/middleware/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ function escapeRegExp(str) {
* @property {Array} [headers] Array of header names.
* @property {Array} [params] Array of param names.
* @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 {String} [currentUserLiteral] String literal for the current user.
* @header loopback.token([options])
Expand All @@ -80,6 +82,9 @@ function token(options) {
currentUserLiteral = escapeRegExp(currentUserLiteral);
}

var enableDoublecheck = !!options.enableDoublecheck;
var overwriteExistingToken = !!options.overwriteExistingToken;

return function(req, res, next) {
var app = req.app;
var registry = app.registry;
Expand All @@ -97,8 +102,19 @@ function token(options) {
'loopback.token() middleware requires a AccessToken model');

if (req.accessToken !== undefined) {
rewriteUserLiteral(req, currentUserLiteral);
return next();
if (!enableDoublecheck) {
// 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) {
req.accessToken = token || null;
Expand Down
86 changes: 85 additions & 1 deletion test/access-token.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('loopback.token(options)', function() {
.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) {
var token = this.token.id;
token = 'Basic ' + new Buffer(token).toString('base64');
Expand Down Expand Up @@ -199,6 +199,90 @@ describe('loopback.token(options)', function() {
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() {
Expand Down