Merge pull request #3103 from strongloop/backport/preserve-current-access-token

Preserve current session when invalidating tokens
This commit is contained in:
Miroslav Bajtoš 2017-01-16 12:00:08 +01:00 committed by GitHub
commit c7d07b6600
3 changed files with 85 additions and 18 deletions

View File

@ -647,7 +647,12 @@ module.exports = function(User) {
throw err;
};
User._invalidateAccessTokensOfUsers = function(userIds, cb) {
User._invalidateAccessTokensOfUsers = function(userIds, options, cb) {
if (typeof options === 'function' && cb === undefined) {
cb = options;
options = {};
}
if (!Array.isArray(userIds) || !userIds.length)
return process.nextTick(cb);
@ -656,7 +661,14 @@ module.exports = function(User) {
return process.nextTick(cb);
var AccessToken = accessTokenRelation.modelTo;
AccessToken.deleteAll({userId: {inq: userIds}}, cb);
var query = {userId: {inq: userIds}};
var tokenPK = AccessToken.definition.idName() || 'id';
if (options.accessToken && tokenPK in options.accessToken) {
query[tokenPK] = {neq: options.accessToken[tokenPK]};
}
AccessToken.deleteAll(query, options, cb);
};
/*!
@ -873,7 +885,7 @@ module.exports = function(User) {
}).map(function(u) {
return u.id;
});
ctx.Model._invalidateAccessTokensOfUsers(userIdsToExpire, next);
ctx.Model._invalidateAccessTokensOfUsers(userIdsToExpire, ctx.options, next);
});
};

View File

@ -100,6 +100,54 @@ describe('users - integration', function() {
done();
});
});
it('invalidates current session when options are not injected', function(done) {
// "injectOptionsFromRemoteContext" is disabled by default,
// therefore the code invalidating sessions cannot tell what
// is the current session, and thus invalidates all sessions
var url = '/api/users/' + userId;
var self = this;
this.request.patch(url)
.send({email: 'new@example.com'})
.set('Authorization', accessToken)
.expect(200, function(err) {
if (err) return done(err);
self.get(url)
.set('Authorization', accessToken)
.expect(401, done);
});
});
it('should preserve current session when invalidating tokens', function(done) {
var UserWithContext = app.registry.createModel({
name: 'UserWithContext',
plural: 'ctx-users',
base: 'User',
injectOptionsFromRemoteContext: true
});
app.model(UserWithContext, {dataSource: 'db'});
var self = this;
var CREDENTIALS = {email: 'ctx@example.com', password: 'pass'};
UserWithContext.create(CREDENTIALS, function(err, user) {
if (err) return done(err);
UserWithContext.login(CREDENTIALS, function(err, token) {
if (err) return done(err);
var url = '/api/ctx-users/' + user.id;
self.request.patch(url)
.send({email: 'new@example.com'})
.set('Authorization', token.id)
.expect(200, function(err) {
if (err) return done(err);
self.get(url)
.set('Authorization', token.id)
.expect(200, done);
});
});
});
});
});
describe('sub-user', function() {

View File

@ -1989,7 +1989,7 @@ describe('User', function() {
User.login(currentEmailCredentials, function(err, accessToken1) {
if (err) return next(err);
assert(accessToken1.userId);
originalUserToken1 = accessToken1.id;
originalUserToken1 = accessToken1;
next();
});
},
@ -1997,7 +1997,7 @@ describe('User', function() {
User.login(currentEmailCredentials, function(err, accessToken2) {
if (err) return next(err);
assert(accessToken2.userId);
originalUserToken2 = accessToken2.id;
originalUserToken2 = accessToken2;
next();
});
},
@ -2057,7 +2057,7 @@ describe('User', function() {
it('keeps sessions AS IS if firstName is added using `updateAttributes`', function(done) {
user.updateAttributes({'firstName': 'Janny'}, function(err, userInstance) {
if (err) return done(err);
assertUntouchedTokens(done);
assertPreservedTokens(done);
});
});
@ -2068,7 +2068,7 @@ describe('User', function() {
email: currentEmailCredentials.email,
}, function(err, userInstance) {
if (err) return done(err);
assertUntouchedTokens(done);
assertPreservedTokens(done);
});
});
@ -2252,6 +2252,19 @@ describe('User', function() {
});
});
it('preserves current session', function(done) {
var options = {accessToken: originalUserToken1};
user.updateAttribute('email', 'new@example.com', options, function(err) {
if (err) return done(err);
AccessToken.find({where: {userId: user.id}}, function(err, tokens) {
if (err) return done(err);
var tokenIds = tokens.map(function(t) { return t.id; });
expect(tokenIds).to.eql([originalUserToken1.id]);
done();
});
});
});
it('preserves other user sessions if their password is untouched', function(done) {
var user1, user2, user1Token;
async.series([
@ -2303,9 +2316,11 @@ describe('User', function() {
function assertPreservedTokens(done) {
AccessToken.find({where: {userId: user.id}}, function(err, tokens) {
if (err) return done(err);
expect(tokens.length).to.equal(2);
expect([tokens[0].id, tokens[1].id]).to.have.members([originalUserToken1,
originalUserToken2]);
var actualIds = tokens.map(function(t) { return t.id; });
actualIds.sort();
var expectedIds = [originalUserToken1.id, originalUserToken2.id];
expectedIds.sort();
expect(actualIds).to.eql(expectedIds);
done();
});
}
@ -2317,14 +2332,6 @@ describe('User', function() {
done();
});
}
function assertUntouchedTokens(done) {
AccessToken.find({where: {userId: user.id}}, function(err, tokens) {
if (err) return done(err);
expect(tokens.length).to.equal(2);
done();
});
}
});
describe('Verification after updating email', function() {