Skip to content

Commit 29a17f3

Browse files
committed
Invalidate AccessTokens on password change
Invalidate all existing sessions (delete all access tokens) after user's password was changed.
1 parent dac1295 commit 29a17f3

File tree

2 files changed

+309
-272
lines changed

2 files changed

+309
-272
lines changed

common/models/user.js

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,19 @@ module.exports = function(User) {
648648
err.statusCode = 422;
649649
throw err;
650650
};
651+
652+
User._invalidateAccessTokensOfUsers = function(userIds, cb) {
653+
if (!Array.isArray(userIds) || !userIds.length)
654+
return process.nextTick(cb);
655+
656+
var accessTokenRelation = this.relations.accessTokens;
657+
if (!accessTokenRelation)
658+
return process.nextTick(cb);
659+
660+
var AccessToken = accessTokenRelation.modelTo;
661+
AccessToken.deleteAll({userId: {inq: userIds}}, cb);
662+
};
663+
651664
/*!
652665
* Setup an extended user model.
653666
*/
@@ -823,6 +836,18 @@ module.exports = function(User) {
823836
if (ctx.isNewInstance) return next();
824837
if (!ctx.where && !ctx.instance) return next();
825838
var where = ctx.where || {id: ctx.instance.id};
839+
840+
var isPartialUpdateChangingPassword = ctx.data && 'password' in ctx.data;
841+
842+
// Full replace of User instance => assume password change.
843+
// HashPassword returns a different value for each invocation,
844+
// therefore we cannot tell whether ctx.instance.password is the same
845+
// or not.
846+
var isFullReplaceChangingPassword = !!ctx.instance;
847+
848+
ctx.hookState.isPasswordChange = isPartialUpdateChangingPassword ||
849+
isFullReplaceChangingPassword;
850+
826851
ctx.Model.find({where: where}, function(err, userInstances) {
827852
if (err) return next(err);
828853
ctx.hookState.originalUserData = userInstances.map(function(u) {
@@ -841,24 +866,26 @@ module.exports = function(User) {
841866
ctx.data.emailVerified = false;
842867
}
843868
}
869+
844870
next();
845871
});
846872
});
847873

848874
User.observe('after save', function afterEmailUpdate(ctx, next) {
849-
if (!ctx.Model.relations.accessTokens) return next();
850-
var AccessToken = ctx.Model.relations.accessTokens.modelTo;
851875
if (!ctx.instance && !ctx.data) return next();
852-
var newEmail = (ctx.instance || ctx.data).email;
853-
if (!newEmail) return next();
854876
if (!ctx.hookState.originalUserData) return next();
855-
var idsToExpire = ctx.hookState.originalUserData.filter(function(u) {
856-
return u.email !== newEmail;
877+
878+
var newEmail = (ctx.instance || ctx.data).email;
879+
var isPasswordChange = ctx.hookState.isPasswordChange;
880+
881+
if (!newEmail && !isPasswordChange) return next();
882+
883+
var userIdsToExpire = ctx.hookState.originalUserData.filter(function(u) {
884+
return (newEmail && u.email !== newEmail) || isPasswordChange;
857885
}).map(function(u) {
858886
return u.id;
859887
});
860-
if (!idsToExpire.length) return next();
861-
AccessToken.deleteAll({userId: {inq: idsToExpire}}, next);
888+
ctx.Model._invalidateAccessTokensOfUsers(userIdsToExpire, next);
862889
});
863890
};
864891

0 commit comments

Comments
 (0)