Merge pull request #1804 from richardpringle/master

Add case-sensitive email option for User model
This commit is contained in:
Miroslav Bajtoš 2015-12-08 12:47:58 +01:00
commit 6d040a98ae
4 changed files with 98 additions and 1 deletions

0
.strong-pm/env.json Normal file
View File

View File

@ -62,6 +62,7 @@ var debug = require('debug')('loopback:user');
* @property {String} settings.realmDelimiter When set a realm is required.
* @property {Number} settings.resetPasswordTokenTTL Time to live for password reset `AccessToken`. Default is `900` (15 minutes).
* @property {Number} settings.saltWorkFactor The `bcrypt` salt work factor. Default is `10`.
* @property {Boolean} settings.caseSensitiveEmail Enable case sensitive email.
*
* @class User
* @inherits {PersistedModel}
@ -577,6 +578,14 @@ module.exports = function(User) {
this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL;
this.settings.ttl = this.settings.ttl || DEFAULT_TTL;
UserModel.setter.email = function(value) {
if (!UserModel.settings.caseSensitiveEmail) {
this.$email = value.toLowerCase();
} else {
this.$email = value;
}
};
UserModel.setter.password = function(plain) {
if (typeof plain !== 'string') {
return;
@ -590,6 +599,14 @@ module.exports = function(User) {
}
};
// Access token to normalize email credentials
UserModel.observe('access', function normalizeEmailCase(ctx, next) {
if (!ctx.Model.settings.caseSensitiveEmail && ctx.query.where && ctx.query.where.email) {
ctx.query.where.email = ctx.query.where.email.toLowerCase();
}
next();
});
// Make sure emailVerified is not set by creation
UserModel.beforeRemote('create', function(ctx, user, next) {
var body = ctx.req.body;

View File

@ -29,6 +29,9 @@
"created": "date",
"lastUpdated": "date"
},
"options": {
"caseSensitiveEmail": true
},
"hidden": ["password"],
"acls": [
{

View File

@ -15,6 +15,7 @@ describe('User', function() {
var validCredentialsEmailVerifiedOverREST = {email: 'foo2@bar.com', password: 'bar2', emailVerified: true};
var validCredentialsWithTTL = {email: 'foo@bar.com', password: 'bar', ttl: 3600};
var validCredentialsWithTTLAndScope = {email: 'foo@bar.com', password: 'bar', ttl: 3600, scope: 'all'};
var validMixedCaseEmailCredentials = {email: 'Foo@bar.com', password: 'bar'};
var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};
var incompleteCredentials = {password: 'bar1'};
@ -67,6 +68,26 @@ describe('User', function() {
});
});
it('Create a new user (email case-sensitivity off)', function(done) {
User.settings.caseSensitiveEmail = false;
User.create({email: 'F@b.com', password: 'bar'}, function(err, user) {
if (err) return done(err);
assert(user.id);
assert.equal(user.email, user.email.toLowerCase());
done();
});
});
it('Create a new user (email case-sensitive)', function(done) {
User.create({email: 'F@b.com', password: 'bar'}, function(err, user) {
if (err) return done(err);
assert(user.id);
assert(user.email);
assert.notEqual(user.email, user.email.toLowerCase());
done();
});
});
it('credentials/challenges are object types', function(done) {
User.create({email: 'f1@b.com', password: 'bar1',
credentials: {cert: 'xxxxx', key: '111'},
@ -124,6 +145,27 @@ describe('User', function() {
});
});
it('Requires a unique email (email case-sensitivity off)', function(done) {
User.settings.caseSensitiveEmail = false;
User.create({email: 'A@b.com', password: 'foobar'}, function(err) {
if (err) return done(err);
User.create({email: 'a@b.com', password: 'batbaz'}, function(err) {
assert(err, 'should error because the email is not unique!');
done();
});
});
});
it('Requires a unique email (email case-sensitive)', function(done) {
User.create({email: 'A@b.com', password: 'foobar'}, function(err, user1) {
User.create({email: 'a@b.com', password: 'batbaz'}, function(err, user2) {
if (err) return done(err);
assert.notEqual(user1.email, user2.email);
done();
});
});
});
it('Requires a unique username', function(done) {
User.create({email: 'a@b.com', username: 'abc', password: 'foobar'}, function() {
User.create({email: 'b@b.com', username: 'abc', password: 'batbaz'}, function(err) {
@ -212,6 +254,25 @@ describe('User', function() {
});
});
describe('Access-hook for queries with email NOT case-sensitive', function() {
it('Should not throw an error if the query does not contain {where: }', function(done) {
User.find({}, function(err) {
if (err) done(err);
done();
});
});
it('Should be able to find lowercase email with mixed-case email query', function(done) {
User.settings.caseSensitiveEmail = false;
User.find({where:{email: validMixedCaseEmailCredentials.email}}, function(err, result) {
if (err) done(err);
assert(result[0], 'The query did not find the user');
assert.equal(result[0].email, validCredentialsEmail);
done();
});
});
});
describe('User.login', function() {
it('Login a user by providing credentials', function(done) {
User.login(validCredentials, function(err, accessToken) {
@ -223,6 +284,23 @@ describe('User', function() {
});
});
it('Login a user by providing email credentials (email case-sensitivity off)', function(done) {
User.settings.caseSensitiveEmail = false;
User.login(validMixedCaseEmailCredentials, function(err, accessToken) {
assert(accessToken.userId);
assert(accessToken.id);
assert.equal(accessToken.id.length, 64);
done();
});
});
it('Try to login with invalid email case', function(done) {
User.login(validMixedCaseEmailCredentials, function(err, accessToken) {
assert(err);
done();
});
});
it('Login a user by providing credentials with TTL', function(done) {
User.login(validCredentialsWithTTL, function(err, accessToken) {
assert(accessToken.userId);
@ -477,7 +555,6 @@ describe('User', function() {
done();
});
});
});
function assertGoodToken(accessToken) {