diff --git a/common/models/user.js b/common/models/user.js index 27e1c9bd..92df8f0f 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -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); }); }; diff --git a/test/user.integration.js b/test/user.integration.js index 46f597c5..45ea04b9 100644 --- a/test/user.integration.js +++ b/test/user.integration.js @@ -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() { diff --git a/test/user.test.js b/test/user.test.js index bba023fe..01c42e7f 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -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() {