Repository: liberapay/liberapay.com

Issue:
Fix sign-in
We seem to have a problem with old cookies from a different account preventing log in.

Goal page is not accessible once connected
"405 Method Not Allowed"
on this page 
https://liberapay.com/unisson/goal.html when i want to change my goal
I'm connected on my account.

Fix sign-in
We seem to have a problem with old cookies from a different account preventing log in.



Discussion:

I'm confused as to how you ended up in this situation @SimonSarazin, changing a team's goal works fine.



Resolving patch:
diff --git a/liberapay/security/authentication.py b/liberapay/security/authentication.py
--- a/liberapay/security/authentication.py
+++ b/liberapay/security/authentication.py
@@ -19,12 +19,15 @@ class _ANON(object):
 ANON = _ANON()
 
 
-def sign_in(request, state):
+def sign_in_with_form_data(request, state):
     try:
         body = request.body
     except Response:
         return
 
+    if not isinstance(body, dict):
+        return
+
     p = None
 
     if body.get('log-in.username'):
@@ -49,12 +52,7 @@ def sign_in(request, state):
             p.add_email(body.pop('sign-in.email'), cursor=c)
         p.authenticated = True
 
-    if p:
-        response = state.setdefault('response', Response())
-        p.sign_in(response.headers.cookie)
-        if body.pop('form.repost', None) != 'true':
-            response.redirect(request.line.uri)
-        state['user'] = p
+    return p
 
 
 def start_user_as_anon():
@@ -68,6 +66,8 @@ def authenticate_user_if_possible(request, state, user):
     """
     if request.line.uri.startswith('/assets/'):
         return
+
+    # HTTP auth
     if 'Authorization' in request.headers:
         header = request.headers['authorization']
         if not header.startswith('Basic '):
@@ -80,13 +80,26 @@ def authenticate_user_if_possible(request, state, user):
         if not participant:
             raise Response(401)
         return {'user': participant}
-    elif SESSION in request.headers.cookie:
+
+    # Cookie and form auth
+    # We want to try cookie auth first, but we want form auth to supersede it
+    p = None
+    response = state.setdefault('response', Response())
+    if SESSION in request.headers.cookie:
         creds = request.headers.cookie[SESSION].value.split(':', 1)
         p = Participant.authenticate('id', 'session', *creds)
         if p:
-            return {'user': p}
-    elif request.method == 'POST':
-        sign_in(request, state)
+            state['user'] = p
+    if request.method == 'POST':
+        old_p = p
+        p = sign_in_with_form_data(request, state)
+        if p:
+            if old_p:
+                old_p.sign_out(response.headers.cookie)
+            p.sign_in(response.headers.cookie)
+            state['user'] = p
+            if request.body.pop('form.repost', None) != 'true':
+                response.redirect(request.line.uri)
 
 
 def add_auth_to_response(response, request=None, user=ANON):


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
@@ -2,6 +2,8 @@
 
 from __future__ import division, print_function, unicode_literals
 
+from six.moves.http_cookies import SimpleCookie
+
 from liberapay.constants import SESSION
 from liberapay.models.participant import Participant
 from liberapay.testing.emails import EmailHarness
@@ -18,15 +20,19 @@
 
 class TestSignIn(EmailHarness):
 
-    def log_in(self, username, password):
+    def log_in(self, username, password, **kw):
         data = {'log-in.username': username, 'log-in.password': password}
-        return self.client.POST('/sign-in', data, raise_immediately=False)
+        return self.client.POST('/sign-in', data, raise_immediately=False, **kw)
 
     def test_log_in(self):
         password = 'password'
         alice = self.make_participant('alice')
         alice.update_password(password)
-        r = self.log_in('alice', password)
+        bob = self.make_participant('bob')
+        bob.authenticated = True
+        cookies = SimpleCookie()
+        bob.sign_in(cookies)
+        r = self.log_in('alice', password, cookies=cookies)
         assert r.code == 302
         assert SESSION in r.headers.cookie