Skip to content

Commit

Permalink
Merge 50f4d87 into 78161cc
Browse files Browse the repository at this point in the history
  • Loading branch information
dhmlau committed Apr 7, 2017
2 parents 78161cc + 50f4d87 commit 8d6e70a
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 12 deletions.
2 changes: 1 addition & 1 deletion common/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,7 @@ module.exports = function(User) {
where[pkName] = ctx.instance[pkName];
}

ctx.Model.find({where: where}, function(err, userInstances) {
ctx.Model.find({where: where}, ctx.options, function(err, userInstances) {
if (err) return next(err);
ctx.hookState.originalUserData = userInstances.map(function(u) {
var user = {};
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"grunt-karma": "^2.0.0",
"grunt-mocha-test": "^0.12.7",
"karma": "^1.1.2",
"karma-browserify": "^4.4.2",
"karma-browserify": "^5.1.1",
"karma-chrome-launcher": "^1.0.1",
"karma-firefox-launcher": "^1.0.0",
"karma-html2js-preprocessor": "^1.0.0",
Expand Down
38 changes: 28 additions & 10 deletions server/middleware/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* Module dependencies.
*/

'use strict';
var g = require('strong-globalize')();
var loopback = require('../../lib/loopback');
var assert = require('assert');
var debug = require('debug')('loopback:middleware:token');
Expand All @@ -20,18 +22,33 @@ module.exports = token;
/*
* Rewrite the url to replace current user literal with the logged in user id
*/
function rewriteUserLiteral(req, currentUserLiteral) {
if (req.accessToken && req.accessToken.userId && currentUserLiteral) {
function rewriteUserLiteral(req, currentUserLiteral, next) {
if (!currentUserLiteral) return next();
var literalRegExp = new RegExp('/' + currentUserLiteral + '(/|$|\\?)', 'g');

if (req.accessToken && req.accessToken.userId) {
// Replace /me/ with /current-user-id/
var urlBeforeRewrite = req.url;
req.url = req.url.replace(
new RegExp('/' + currentUserLiteral + '(/|$|\\?)', 'g'),
req.url = req.url.replace(literalRegExp,
'/' + req.accessToken.userId + '$1');

if (req.url !== urlBeforeRewrite) {
debug('req.url has been rewritten from %s to %s', urlBeforeRewrite,
req.url);
}
} else if (!req.accessToken && literalRegExp.test(req.url)) {
debug(
'URL %s matches current-user literal %s,' +
' but no (valid) access token was provided.',
req.url, currentUserLiteral);

var e = new Error(g.f('Authorization Required'));
e.status = e.statusCode = 401;
e.code = 'AUTHORIZATION_REQUIRED';
return next(e);
}

next();
}

function escapeRegExp(str) {
Expand Down Expand Up @@ -110,23 +127,24 @@ function token(options) {
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();
return rewriteUserLiteral(req, currentUserLiteral, 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();
return rewriteUserLiteral(req, currentUserLiteral, next);
}
// continue normal operation (as if req.accessToken was undefined)
}

TokenModel.findForRequest(req, options, function(err, token) {
req.accessToken = token || null;
rewriteUserLiteral(req, currentUserLiteral);

var ctx = req.loopbackContext;
if (ctx && ctx.active) ctx.set('accessToken', token);
next(err);

if (err) return next(err);
rewriteUserLiteral(req, currentUserLiteral, next);
});
};
}
20 changes: 20 additions & 0 deletions test/access-token.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,26 @@ describe('loopback.token(options)', function() {
});
});

it('should generate a 401 on a current user literal route without an authToken',
function(done) {
var app = createTestApp(null, done);
request(app)
.get('/users/me')
.set('authorization', null)
.expect(401)
.end(done);
});

it('should generate a 401 on a current user literal route with invalid authToken',
function(done) {
var app = createTestApp(this.token, done);
request(app)
.get('/users/me')
.set('Authorization', 'invald-token-id')
.expect(401)
.end(done);
});

it('should skip when req.token is already present', function(done) {
var tokenStub = { id: 'stub id' };
app.use(function(req, res, next) {
Expand Down
32 changes: 32 additions & 0 deletions test/user.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var loopback = require('../');
var User, AccessToken;
var async = require('async');
var url = require('url');
var extend = require('util')._extend;

describe('User', function() {
this.timeout(10000);
Expand Down Expand Up @@ -2379,6 +2380,37 @@ describe('User', function() {
});
});

it('forwards the "options" argument', function(done) {
var options = {testFlag: true};
var observedOptions = [];

saveObservedOptionsForHook('access', User);
saveObservedOptionsForHook('before delete', AccessToken);

user.updateAttribute('password', 'newPass', options, function(err, updated) {
if (err) return done(err);

expect(observedOptions).to.eql([
// prepareForTokenInvalidation - load current instance data
{hook: 'access', testFlag: true},

// validate uniqueness of User.email
{hook: 'access', testFlag: true},

// _invalidateAccessTokensOfUsers - deleteAll
{hook: 'before delete', testFlag: true},
]);
done();
});

function saveObservedOptionsForHook(name, model) {
model.observe(name, function(ctx, next) {
observedOptions.push(extend({hook: name}, ctx.options));
next();
});
}
});

it('preserves other user sessions if their password is untouched', function(done) {
var user1, user2, user1Token;
async.series([
Expand Down

0 comments on commit 8d6e70a

Please sign in to comment.