Skip to content

Commit

Permalink
Merge pull request #35790 from dimagi/em/email-validator-case-sensiti…
Browse files Browse the repository at this point in the history
…vity

Make email validator case-insensitive
  • Loading branch information
nospame authored Feb 18, 2025
2 parents e71e489 + bcbb8e7 commit c7f3725
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
hqDefine("hqwebapp/js/bootstrap3/validators.ko", [
'jquery',
'knockout',
'hqwebapp/js/constants',
'knockout-validation/dist/knockout.validation.min', // needed for ko.validation
], function (
$,
ko,
constants,
) {
ko.validation.rules['emailRFC2822'] = {
validator: function (val) {
if (val === undefined || val.length === 0) {return true;} // do separate validation for required
var re = /(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/;
var re = constants.EMAIL_VALIDATION_REGEX;
return re.test(val || '') && val.indexOf(' ') < 0;
},
message: gettext("Not a valid email"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
hqDefine("hqwebapp/js/bootstrap5/validators.ko", [
'jquery',
'knockout',
'hqwebapp/js/constants',
'knockout-validation/dist/knockout.validation.min', // needed for ko.validation
], function (
$,
ko,
constants,
) {
ko.validation.init({
errorMessageClass: 'invalid-feedback',
Expand All @@ -16,7 +18,7 @@ hqDefine("hqwebapp/js/bootstrap5/validators.ko", [
ko.validation.rules['emailRFC2822'] = {
validator: function (val) {
if (val === undefined || val.length === 0) {return true;} // do separate validation for required
var re = /(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/;
var re = constants.EMAIL_VALIDATION_REGEX;
return re.test(val || '') && val.indexOf(' ') < 0;
},
message: gettext("Not a valid email"),
Expand Down
6 changes: 6 additions & 0 deletions corehq/apps/hqwebapp/static/hqwebapp/js/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
hqDefine("hqwebapp/js/constants", [], function () {
return {
// eslint-disable-next-line no-control-regex
EMAIL_VALIDATION_REGEX: /(?:[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[A-Za-z0-9-]*[A-Za-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/,
};
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import hqMocha from "mocha/js/main";

import "hqwebapp/spec/assert_properties_spec";
import "hqwebapp/spec/email_validator_spec";
import "hqwebapp/spec/bootstrap3/inactivity_spec";
import "hqwebapp/spec/urllib_spec";
import "hqwebapp/spec/bootstrap3/widgets_spec";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import hqMocha from "mocha/js/main";

import "hqwebapp/spec/assert_properties_spec";
import "hqwebapp/spec/email_validator_spec";
import "hqwebapp/spec/bootstrap5/inactivity_spec";
import "hqwebapp/spec/urllib_spec";
import "hqwebapp/spec/bootstrap5/widgets_spec";
Expand Down
83 changes: 83 additions & 0 deletions corehq/apps/hqwebapp/static/hqwebapp/spec/email_validator_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* eslint-env mocha */
hqDefine("hqwebapp/spec/email_validator_spec", [
'hqwebapp/js/constants',
], function (
constants,
) {
describe('email_validator', function () {
const re = constants.EMAIL_VALIDATION_REGEX;
const testEmail = email => re.test(email);

it('should allow simple email addresses', function () {
assert.ok(testEmail('[email protected]'));
});

it('should allow capital letters in the local part', function () {
assert.ok(testEmail('[email protected]'));
assert.ok(testEmail('[email protected]'));
});

it('should allow capital letters in the domain part', function () {
assert.ok(testEmail('[email protected]'));
assert.ok(testEmail('[email protected]'));
});

it('should allow digits in the local part', function () {
assert.ok(testEmail('[email protected]'));
});

it('should allow specified special characters in the local part', function () {
assert.ok(testEmail('.\x01!#$%&"\'*+/=?^_`{|}[email protected]'));
});

it('should allow subdomains', function () {
assert.ok(testEmail('[email protected]'));
});

it('should allow hyphens in domain names', function () {
assert.ok(testEmail('[email protected]'));
});

it('should allow IP addresses in brackets as the domain', function () {
assert.ok(testEmail('whosaidthiswasok@[127.0.0.1]'));
});

it('should reject missing @ symbol', function () {
assert.notOk(testEmail('notanemail'));
});

it('should reject missing local part', function () {
assert.notOk(testEmail('@example.com'));
});

it('should reject missing top-level domain', function () {
assert.notOk(testEmail('nothing@toseehere'));
assert.notOk(testEmail('noteven@toseehere.'));
});

it('should reject invalid characters in the domain part', function () {
const emails = [
// allowed in local part but not domain
'me@ex\x01ample.com',
'me@ex!ample.com',
'me@ex#ample.com',
'me@ex$ample.com',
'me@ex%ample.com',
'me@ex&ample.com',
'me@ex"ample.com',
'me@ex\'ample.com',
'me@ex*ample.com',
'me@ex+ample.com',
'me@ex/ample.com',
'me@ex=ample.com',
'me@ex?ample.com',
'me@ex^ample.com',
'me@ex_ample.com',
'me@ex`ample.com',
'me@ex{ample}.com',
'me@ex|ample.com',
];
emails.forEach(email => assert.notOk(testEmail(email)));
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
+hqDefine("hqwebapp/js/bootstrap5/validators.ko", [
'jquery',
'knockout',
'knockout-validation/dist/knockout.validation.min', // needed for ko.validation
@@ -6,6 +6,13 @@
$,
'hqwebapp/js/constants',
@@ -8,6 +8,13 @@
ko,
constants,
) {
+ ko.validation.init({
+ errorMessageClass: 'invalid-feedback',
Expand All @@ -20,7 +20,7 @@
ko.validation.rules['emailRFC2822'] = {
validator: function (val) {
if (val === undefined || val.length === 0) {return true;} // do separate validation for required
@@ -19,52 +26,71 @@
@@ -21,52 +28,71 @@

/**
* Use this handler to show bootstrap validation states on a form input when
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
+++
@@ -1,8 +1,8 @@
import hqMocha from "mocha/js/main";
@@ -2,8 +2,8 @@

import "hqwebapp/spec/assert_properties_spec";
import "hqwebapp/spec/email_validator_spec";
-import "hqwebapp/spec/bootstrap3/inactivity_spec";
+import "hqwebapp/spec/bootstrap5/inactivity_spec";
import "hqwebapp/spec/urllib_spec";
Expand Down
2 changes: 1 addition & 1 deletion corehq/apps/registration/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ def __init__(self, data=None, is_add_user=None,
)

def clean_email(self):
email = self.cleaned_data['email'].strip()
email = self.cleaned_data['email'].strip().lower()

from corehq.apps.registration.validation import AdminInvitesUserFormValidator
error = AdminInvitesUserFormValidator.validate_email(self.domain, email, bool(self.invite))
Expand Down

0 comments on commit c7f3725

Please sign in to comment.