Skip to content

Commit 2cca83c

Browse files
author
Richard Pringle
committed
Add case-sensitve email option for User model.
1 parent bfd5059 commit 2cca83c

4 files changed

Lines changed: 98 additions & 1 deletion

File tree

‎.strong-pm/env.json‎

Whitespace-only changes.

‎common/models/user.js‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ var debug = require('debug')('loopback:user');
5858
* @property {String} settings.realmDelimiter When set a realm is required.
5959
* @property {Number} settings.resetPasswordTokenTTL Time to live for password reset `AccessToken`. Default is `900` (15 minutes).
6060
* @property {Number} settings.saltWorkFactor The `bcrypt` salt work factor. Default is `10`.
61+
* @property {Boolean} settings.caseSensitiveEmail Enable case sensitive email.
6162
*
6263
* @class User
6364
* @inherits {PersistedModel}
@@ -573,6 +574,14 @@ module.exports = function(User) {
573574
this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL;
574575
this.settings.ttl = this.settings.ttl || DEFAULT_TTL;
575576

577+
UserModel.setter.email = function(value) {
578+
if (!UserModel.settings.caseSensitiveEmail) {
579+
this.$email = value.toLowerCase();
580+
} else {
581+
this.$email = value;
582+
}
583+
};
584+
576585
UserModel.setter.password = function(plain) {
577586
if (typeof plain !== 'string') {
578587
return;
@@ -586,6 +595,14 @@ module.exports = function(User) {
586595
}
587596
};
588597

598+
// Access token to normalize email credentials
599+
UserModel.observe('access', function normalizeEmailCase(ctx, next) {
600+
if (!ctx.Model.settings.caseSensitiveEmail && ctx.query.where && ctx.query.where.email) {
601+
ctx.query.where.email = ctx.query.where.email.toLowerCase();
602+
}
603+
next();
604+
});
605+
589606
// Make sure emailVerified is not set by creation
590607
UserModel.beforeRemote('create', function(ctx, user, next) {
591608
var body = ctx.req.body;

‎common/models/user.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
"created": "date",
3030
"lastUpdated": "date"
3131
},
32+
"options": {
33+
"caseSensitiveEmail": true
34+
},
3235
"hidden": ["password"],
3336
"acls": [
3437
{

‎test/user.test.js‎

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe('User', function() {
1515
var validCredentialsEmailVerifiedOverREST = {email: 'foo2@bar.com', password: 'bar2', emailVerified: true};
1616
var validCredentialsWithTTL = {email: 'foo@bar.com', password: 'bar', ttl: 3600};
1717
var validCredentialsWithTTLAndScope = {email: 'foo@bar.com', password: 'bar', ttl: 3600, scope: 'all'};
18+
var validMixedCaseEmailCredentials = {email: 'Foo@bar.com', password: 'bar'};
1819
var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};
1920
var incompleteCredentials = {password: 'bar1'};
2021

@@ -67,6 +68,26 @@ describe('User', function() {
6768
});
6869
});
6970

71+
it('Create a new user (email case-sensitivity off)', function(done) {
72+
User.settings.caseSensitiveEmail = false;
73+
User.create({email: 'F@b.com', password: 'bar'}, function(err, user) {
74+
if (err) return done(err);
75+
assert(user.id);
76+
assert.equal(user.email, user.email.toLowerCase());
77+
done();
78+
});
79+
});
80+
81+
it('Create a new user (email case-sensitive)', function(done) {
82+
User.create({email: 'F@b.com', password: 'bar'}, function(err, user) {
83+
if (err) return done(err);
84+
assert(user.id);
85+
assert(user.email);
86+
assert.notEqual(user.email, user.email.toLowerCase());
87+
done();
88+
});
89+
});
90+
7091
it('credentials/challenges are object types', function(done) {
7192
User.create({email: 'f1@b.com', password: 'bar1',
7293
credentials: {cert: 'xxxxx', key: '111'},
@@ -124,6 +145,27 @@ describe('User', function() {
124145
});
125146
});
126147

148+
it('Requires a unique email (email case-sensitivity off)', function(done) {
149+
User.settings.caseSensitiveEmail = false;
150+
User.create({email: 'A@b.com', password: 'foobar'}, function(err) {
151+
if (err) return done(err);
152+
User.create({email: 'a@b.com', password: 'batbaz'}, function(err) {
153+
assert(err, 'should error because the email is not unique!');
154+
done();
155+
});
156+
});
157+
});
158+
159+
it('Requires a unique email (email case-sensitive)', function(done) {
160+
User.create({email: 'A@b.com', password: 'foobar'}, function(err, user1) {
161+
User.create({email: 'a@b.com', password: 'batbaz'}, function(err, user2) {
162+
if (err) return done(err);
163+
assert.notEqual(user1.email, user2.email);
164+
done();
165+
});
166+
});
167+
});
168+
127169
it('Requires a unique username', function(done) {
128170
User.create({email: 'a@b.com', username: 'abc', password: 'foobar'}, function() {
129171
User.create({email: 'b@b.com', username: 'abc', password: 'batbaz'}, function(err) {
@@ -212,6 +254,25 @@ describe('User', function() {
212254
});
213255
});
214256

257+
describe('Access-hook for queries with email NOT case-sensitive', function() {
258+
it('Should not throw an error if the query does not contain {where: }', function(done) {
259+
User.find({}, function(err) {
260+
if (err) done(err);
261+
done();
262+
});
263+
});
264+
265+
it('Should be able to find lowercase email with mixed-case email query', function(done) {
266+
User.settings.caseSensitiveEmail = false;
267+
User.find({where:{email: validMixedCaseEmailCredentials.email}}, function(err, result) {
268+
if (err) done(err);
269+
assert(result[0], 'The query did not find the user');
270+
assert.equal(result[0].email, validCredentialsEmail);
271+
done();
272+
});
273+
});
274+
});
275+
215276
describe('User.login', function() {
216277
it('Login a user by providing credentials', function(done) {
217278
User.login(validCredentials, function(err, accessToken) {
@@ -223,6 +284,23 @@ describe('User', function() {
223284
});
224285
});
225286

287+
it('Login a user by providing email credentials (email case-sensitivity off)', function(done) {
288+
User.settings.caseSensitiveEmail = false;
289+
User.login(validMixedCaseEmailCredentials, function(err, accessToken) {
290+
assert(accessToken.userId);
291+
assert(accessToken.id);
292+
assert.equal(accessToken.id.length, 64);
293+
done();
294+
});
295+
});
296+
297+
it('Try to login with invalid email case', function(done) {
298+
User.login(validMixedCaseEmailCredentials, function(err, accessToken) {
299+
assert(err);
300+
done();
301+
});
302+
});
303+
226304
it('Login a user by providing credentials with TTL', function(done) {
227305
User.login(validCredentialsWithTTL, function(err, accessToken) {
228306
assert(accessToken.userId);
@@ -477,7 +555,6 @@ describe('User', function() {
477555
done();
478556
});
479557
});
480-
481558
});
482559

483560
function assertGoodToken(accessToken) {

0 commit comments

Comments
 (0)