Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .strong-pm/env.json
Empty file.
17 changes: 17 additions & 0 deletions common/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions common/models/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
"created": "date",
"lastUpdated": "date"
},
"options": {
"caseSensitiveEmail": true
},
"hidden": ["password"],
"acls": [
{
Expand Down
79 changes: 78 additions & 1 deletion test/user.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'};

Expand Down Expand Up @@ -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'},
Expand Down Expand Up @@ -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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handler.

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) {
Expand Down Expand Up @@ -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();
});
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bajtos, does this look good? Wasn't too sure about case description...


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) {
Expand All @@ -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);
Expand Down Expand Up @@ -477,7 +555,6 @@ describe('User', function() {
done();
});
});

});

function assertGoodToken(accessToken) {
Expand Down