From afd6dd707361981c9b2b47005677316d97396158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 13 Jan 2017 11:03:06 +0100 Subject: [PATCH] Preserve current session when invalidating tokens Fix User model to preserve the current session (provided via "options.accessToken") when invalidating access tokens after a change of email or password property. --- common/models/user.js | 18 ++++++++++++--- test/user.integration.js | 48 ++++++++++++++++++++++++++++++++++++++++ test/user.test.js | 13 +++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) 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 808575f4..01c42e7f 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -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([