Repository: liberapay/liberapay.com

Issue:
Email field is case sensitive.
Hello, I just tried to log into my account, and It seem that putting an caps inside one letter is actually considered as another email (on phone, the first letter is a lot of time caps'ed so it's silly to prevent it)
I think this should not be case sensitive, since emails aren't case sensitive in general :)

Steps to reproduce : 
 - Register
 - Log out
 - Try to log-in using different caps inside the email field from the one you registered.


Discussion:
Strictly speaking the first part of an email address is case-sensitive, however in practice doing a case-insensitive lookup should be fine.

Resolving patch:
diff --git a/liberapay/models/participant.py b/liberapay/models/participant.py
--- a/liberapay/models/participant.py
+++ b/liberapay/models/participant.py
@@ -151,16 +151,16 @@ def from_username(cls, username):
 
     @classmethod
     def _from_thing(cls, thing, value):
-        assert thing in ("id", "lower(username)", "mangopay_user_id", "email")
-        if thing == 'email':
+        assert thing in ("id", "lower(username)", "mangopay_user_id", "lower(email)")
+        if thing == 'lower(email)':
             # This query looks for an unverified address if the participant
             # doesn't have any verified address
             return cls.db.one("""
                 SELECT p.*::participants
                   FROM emails e
                   JOIN participants p ON p.id = e.participant
-                 WHERE e.address = %s
-                   AND (p.email IS NULL OR p.email = e.address)
+                 WHERE lower(e.address) = %s
+                   AND (p.email IS NULL OR lower(p.email) = lower(e.address))
               ORDER BY p.email NULLS LAST, p.id ASC
                  LIMIT 1
             """, (value,))
@@ -175,8 +175,8 @@ def authenticate(cls, k1, k2, v1=None, v2=None):
         assert k1 in ('id', 'username', 'email')
         if not (v1 and v2):
             return
-        if k1 == 'username':
-            k1 = 'lower(username)'
+        if k1 in ('username', 'email'):
+            k1 = 'lower(%s)' % k1
             v1 = v1.lower()
         p = cls._from_thing(k1, v1)
         if not p:
diff --git a/liberapay/security/authentication.py b/liberapay/security/authentication.py
--- a/liberapay/security/authentication.py
+++ b/liberapay/security/authentication.py
@@ -53,7 +53,7 @@ def sign_in_with_form_data(body, state):
             return
         else:
             email = id
-            p = Participant._from_thing('email', email)
+            p = Participant._from_thing('lower(email)', email.lower())
             if p:
                 p.start_session()
                 qs = {'log-in.id': p.id, 'log-in.token': p.session_token}


Test patch:
diff --git a/tests/py/test_sign_in.py b/tests/py/test_sign_in.py
--- a/tests/py/test_sign_in.py
+++ b/tests/py/test_sign_in.py
@@ -133,7 +133,7 @@ def test_email_login(self):
         alice.add_email(email)
         alice.close(None)
 
-        data = {'log-in.id': email}
+        data = {'log-in.id': email.upper()}
         r = self.client.POST('/', data, raise_immediately=False)
         alice = alice.refetch()
         assert alice.session_token not in r.headers.raw.decode('ascii')