From 619372e51e1fce6360411feed3c478ac804ed113 Mon Sep 17 00:00:00 2001 From: Loay Date: Thu, 7 Jul 2016 11:30:57 -0400 Subject: [PATCH] Backport/Fix security issue 580 --- common/models/user.js | 14 +++++++ test/user.test.js | 87 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/common/models/user.js b/common/models/user.js index 09bdcb74..1a5e6e8c 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -302,6 +302,20 @@ module.exports = function(User) { return fn.promise; }; + User.observe('before delete', function(ctx, next) { + var AccessToken = ctx.Model.relations.accessTokens.modelTo; + var pkName = ctx.Model.definition.idName() || 'id'; + ctx.Model.find({ where: ctx.where, fields: [pkName] }, function(err, list) { + if (err) return next(err); + + var ids = list.map(function(u) { return u[pkName]; }); + ctx.where = {}; + ctx.where[pkName] = { inq: ids }; + + AccessToken.destroyAll({ userId: { inq: ids }}, next); + }); + }); + /** * Compare the given `password` with the users hashed password. * diff --git a/test/user.test.js b/test/user.test.js index 2fd77870..ade96e4a 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -6,6 +6,7 @@ require('./support'); var loopback = require('../'); var User, AccessToken; +var async = require('async'); describe('User', function() { var validCredentialsEmail = 'foo@bar.com'; @@ -227,6 +228,92 @@ describe('User', function() { assert(u2.password === u1.password); }); + it('invalidates the user\'s accessToken when the user is deleted By id', function(done) { + var usersId; + async.series([ + function(next) { + User.create({ email: 'b@c.com', password: 'bar' }, function(err, user) { + usersId = user.id; + next(err); + }); + }, + function(next) { + User.login({ email: 'b@c.com', password: 'bar' }, function(err, accessToken) { + if (err) return next(err); + assert(accessToken.userId); + next(); + }); + }, + function(next) { + User.deleteById(usersId, function(err) { + next(err); + }); + }, + function(next) { + User.findById(usersId, function(err, userFound) { + if (err) return next(err); + expect(userFound).to.equal(null); + AccessToken.find({ where: { userId: usersId }}, function(err, tokens) { + if (err) return next(err); + expect(tokens.length).to.equal(0); + next(); + }); + }); + }, + ], function(err) { + if (err) return done(err); + done(); + }); + }); + + it('invalidates the user\'s accessToken when the user is deleted all', function(done) { + var usersId, accessTokenId; + async.series([ + function(next) { + User.create([{ name: 'myname', email: 'b@c.com', password: 'bar' }, + { name: 'myname', email: 'd@c.com', password: 'bar' }], function(err, user) { + usersId = user.id; + next(err); + }); + }, + function(next) { + User.login({ email: 'b@c.com', password: 'bar' }, function(err, accessToken) { + accessTokenId = accessToken.userId; + if (err) return next (err); + assert(accessTokenId); + next(); + }); + }, + function(next) { + User.login({ email: 'd@c.com', password: 'bar' }, function(err, accessToken) { + accessTokenId = accessToken.userId; + if (err) return next (err); + assert(accessTokenId); + next(); + }); + }, + function(next) { + User.deleteAll({ name: 'myname' }, function(err, user) { + next(err); + }); + }, + function(next) { + User.find({ where: { name: 'myname' }}, function(err, userFound) { + if (err) return next (err); + expect(userFound.length).to.equal(0); + AccessToken.find({ where: { userId: usersId }}, function(err, tokens) { + if (err) return next(err); + expect(tokens.length).to.equal(0); + next(); + }); + }); + }, + ], function(err) { + if (err) return done(err); + done(); + }); + }); + describe('custom password hash', function() { var defaultHashPassword; var defaultValidatePassword;