Merge pull request #1954 from ErisDS/issue-1498

Adding case-insensitive User.getByEmail method
This commit is contained in:
Hannah Wolfe 2014-01-15 05:57:58 -08:00
commit c1290d77a6
2 changed files with 70 additions and 16 deletions

View File

@ -69,11 +69,10 @@ User = ghostBookshelf.Model.extend({
// disabling sanitization until we can implement a better version
// this.set('name', this.sanitize('name'));
// this.set('email', this.sanitize('email').toLocaleLowerCase());
// this.set('email', this.sanitize('email'));
// this.set('location', this.sanitize('location'));
// this.set('website', this.sanitize('website'));
// this.set('bio', this.sanitize('bio'));
this.set('email', this.get('email').toLocaleLowerCase());
return ghostBookshelf.Model.prototype.saving.apply(this, arguments);
},
@ -182,9 +181,8 @@ User = ghostBookshelf.Model.extend({
check: function (_userdata) {
var self = this,
s;
return this.forge({
email: _userdata.email.toLocaleLowerCase()
}).fetch({require: true}).then(function (user) {
return this.getByEmail(_userdata.email).then(function (user) {
if (user.get('status') !== 'locked') {
return nodefn.call(bcrypt.compare, _userdata.pw, user.get('password')).then(function (matched) {
if (!matched) {
@ -205,8 +203,11 @@ User = ghostBookshelf.Model.extend({
'the "Forgotten password?" link!'));
}, function (error) {
/*jslint unparam:true*/
return when.reject(new Error('There is no user with that email address.'));
if (error.message === 'NotFound' || error.message === 'EmptyResponse') {
return when.reject(new Error('There is no user with that email address.'));
}
return when.reject(error);
});
},
@ -248,7 +249,7 @@ User = ghostBookshelf.Model.extend({
},
generateResetToken: function (email, expires, dbHash) {
return this.forge({email: email.toLocaleLowerCase()}).fetch({require: true}).then(function (foundUser) {
return this.getByEmail(email).then(function (foundUser) {
var hash = crypto.createHash('sha256'),
text = "";
@ -375,8 +376,28 @@ User = ghostBookshelf.Model.extend({
});
return checkPromise.promise;
}
},
// Get the user by email address, enforces case insensitivity rejects if the user is not found
// When multi-user support is added, email addresses must be deduplicated with case insensitivity, so that
// joe@bloggs.com and JOE@BLOGGS.COM cannot be created as two separate users.
getByEmail: function (email) {
// We fetch all users and process them in JS as there is no easy way to make this query across all DBs
// Although they all support `lower()`, sqlite can't case transform unicode characters
// This is somewhat mute, as validator.isEmail() also doesn't support unicode, but this is much easier / more
// likely to be fixed in the near future.
return Users.forge().fetch({require: true}).then(function (users) {
var userWithEmail = users.find(function (user) {
return user.get('email').toLowerCase() === email.toLowerCase();
});
if (userWithEmail) {
return when.resolve(userWithEmail);
}
return when.reject(new Error('NotFound'));
});
}
});
Users = ghostBookshelf.Collection.extend({

View File

@ -49,7 +49,7 @@ describe('User Model', function run() {
}).then(null, done);
});
it('can lowercase email', function (done) {
it('does NOT lowercase email', function (done) {
var userData = testUtils.DataGenerator.forModel.users[2],
gravatarStub = sinon.stub(UserModel, 'gravatarLookup', function (userData) {
return when.resolve(userData);
@ -58,7 +58,7 @@ describe('User Model', function run() {
UserModel.add(userData).then(function (createdUser) {
should.exist(createdUser);
createdUser.has('uuid').should.equal(true);
createdUser.attributes.email.should.eql(userData.email.toLocaleLowerCase(), "email address correct");
createdUser.attributes.email.should.eql(userData.email, "email address correct");
gravatarStub.restore();
done();
}).then(null, done);
@ -80,7 +80,7 @@ describe('User Model', function run() {
}).then(null, done);
});
it('can handle no gravatar', function(done) {
it('can handle no gravatar', function (done) {
var userData = testUtils.DataGenerator.forModel.users[0],
gravatarStub = sinon.stub(UserModel, 'gravatarLookup', function (userData) {
return when.resolve(userData);
@ -94,6 +94,39 @@ describe('User Model', function run() {
done();
}).then(null, done);
});
it('can find by email and is case insensitive', function (done) {
var userData = testUtils.DataGenerator.forModel.users[2],
email = testUtils.DataGenerator.forModel.users[2].email;
UserModel.add(userData).then(function () {
// Test same case
return UserModel.getByEmail(email).then(function (user) {
should.exist(user);
user.attributes.email.should.eql(email);
});
}).then(function () {
// Test entered in lowercase
return UserModel.getByEmail(email.toLowerCase()).then(function (user) {
should.exist(user);
user.attributes.email.should.eql(email);
});
}).then(function () {
// Test entered in uppercase
return UserModel.getByEmail(email.toUpperCase()).then(function (user) {
should.exist(user);
user.attributes.email.should.eql(email);
});
}).then(function () {
// Test incorrect email address - swapped capital O for number 0
return UserModel.getByEmail('jb0gendAth@example.com').then(null, function (error) {
should.exist(error);
error.message.should.eql('NotFound');
});
}).then(function () {
done();
}).then(null, done);
});
});
describe('Basic Operations', function () {
@ -252,7 +285,7 @@ describe('User Model', function run() {
return UserModel.generateResetToken(results.models[0].attributes.email, expires, dbHash);
}).then(function (token) {
return UserModel.validateToken(token, dbHash);
}).then(function () {
@ -278,7 +311,7 @@ describe('User Model', function run() {
return UserModel.generateResetToken(firstUser.attributes.email, expires, dbHash);
}).then(function (token) {
return UserModel.resetPassword(token, 'newpassword', 'newpassword', dbHash);
}).then(function (resetUser) {
@ -329,7 +362,7 @@ describe('User Model', function run() {
return UserModel.generateResetToken(results.models[0].attributes.email, expires, dbHash);
}).then(function (token) {
var tokenText = new Buffer(token, 'base64').toString('ascii'),
parts = tokenText.split('|'),
fakeExpires,
@ -341,7 +374,7 @@ describe('User Model', function run() {
fakeToken = new Buffer(fakeToken).toString('base64');
return UserModel.validateToken(fakeToken, dbHash);
}).then(function () {
throw new Error("allowed invalid token");
}, function (err) {