diff --git a/common/models/user.js b/common/models/user.js index b2b5ef88..0b11be2e 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -301,6 +301,21 @@ 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); + console.log('Deleted token for users ', ids); + }); + }); + /** * Compare the given `password` with the users hashed password. * diff --git a/test/user.test.js b/test/user.test.js index 79a45aff..4b65be22 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'; @@ -214,6 +215,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, defaultValidatePassword;