From 2cca83c4fff718a7fbf0f69e009d985eb9be7c16 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 9 Nov 2015 16:21:38 -0500 Subject: [PATCH] Add case-sensitve email option for User model. --- .strong-pm/env.json | 0 common/models/user.js | 17 +++++++++ common/models/user.json | 3 ++ test/user.test.js | 79 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 .strong-pm/env.json diff --git a/.strong-pm/env.json b/.strong-pm/env.json new file mode 100644 index 00000000..e69de29b diff --git a/common/models/user.js b/common/models/user.js index d53d0998..9b9f93e6 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -58,6 +58,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} @@ -573,6 +574,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; @@ -586,6 +595,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; diff --git a/common/models/user.json b/common/models/user.json index be60d9d8..d70a89d3 100644 --- a/common/models/user.json +++ b/common/models/user.json @@ -29,6 +29,9 @@ "created": "date", "lastUpdated": "date" }, + "options": { + "caseSensitiveEmail": true + }, "hidden": ["password"], "acls": [ { diff --git a/test/user.test.js b/test/user.test.js index 42dc1368..8e2b1494 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -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) {