Repository: gratipay/gratipay.com

Issue:
403 clicking "fix credit card" in email when not logged in
My credit card expired and I got the email reminding me to fix payment info. I clicked the "fix credit card" button in the email and was taken to a 403 Forbidden page. Would expect to be taken to login form when I'm not already logged in. Thanks!



Discussion:
I think the behavior should be something like https://gratipay.com/new when a ~user is not logged in, would that work, @JVimes?

![image](https://cloud.githubusercontent.com/assets/3729038/15428801/2936ce18-1e63-11e6-9106-4f98ea049f8f.png)

rather than 

![image](https://cloud.githubusercontent.com/assets/3729038/15432441/de6136ce-1e73-11e6-9a8f-ef6be4573ee6.png)

Solution to copy: https://github.com/gratipay/gratipay.com/blob/master/emails/charge_failed.spt#L15https://github.com/gratipay/gratipay.com/blob/master/www/new.spt#L8

Link in email: https://github.com/gratipay/gratipay.com/blob/master/emails/charge_failed.spt#L15

To change: https://github.com/gratipay/gratipay.com/blob/master/www/~/%25username/routes/credit-card.spt#L40

@mattbk, sorry for delay. That would be better, but could the styling for the 401 page be customized to put the emphasis on login rather than the HTTP status code? End users aren't concerned with the 401 status, they just want to be presented with the login form as soon as possible.

Probably. Glad you're paying attention--I don't even look at anything in the header anymore.

Not as complicated as https://github.com/gratipay/gratipay.com/issues/4040#issuecomment-220634087 lays out. There was no `403.spt` file, so no special page was shown.

Shouldn't we be raising 401 in this case instead of 403?

401 = not logged in
403 = already logged in but still not allowed

> 401 = not logged in
> 403 = already logged in but still not allowed

Then why do I get 403 when not logged in, for pages like this?
![image](https://cloud.githubusercontent.com/assets/3729038/20439498/52f767aa-ad81-11e6-98fa-c87b47157870.png)

Because we're doing it wrong? :)

Unless we're not, but I think we are ...

https://httpstatuses.com/401
https://httpstatuses.com/403


Resolving patch:
diff --git a/gratipay/utils/__init__.py b/gratipay/utils/__init__.py
--- a/gratipay/utils/__init__.py
+++ b/gratipay/utils/__init__.py
@@ -69,7 +69,7 @@ def get_participant(state, restrict=True, resolve_unclaimed=True):
 
     if restrict:
         if user.ANON:
-            raise Response(403, _("You need to log in to access this page."))
+            raise Response(401, _("You need to log in to access this page."))
 
     from gratipay.models.participant import Participant  # avoid circular import
     participant = Participant.from_username(slug)


Test patch:
diff --git a/tests/py/test_email.py b/tests/py/test_email.py
--- a/tests/py/test_email.py
+++ b/tests/py/test_email.py
@@ -74,9 +74,9 @@ def test_adding_second_email_sends_verification_notice(self):
         expected = "We are connecting alice2@example.com to the alice account on Gratipay"
         assert expected in last_email['body_text']
 
-    def test_post_anon_returns_403(self):
+    def test_post_anon_returns_401(self):
         response = self.hit_email_spt('add-email', 'anon@gratipay.com', user=None, should_fail=True)
-        assert response.code == 403
+        assert response.code == 401
 
     def test_post_with_no_at_symbol_is_400(self):
         response = self.hit_email_spt('add-email', 'gratipay.com', should_fail=True)
diff --git a/tests/py/test_identity_pages.py b/tests/py/test_identity_pages.py
--- a/tests/py/test_identity_pages.py
+++ b/tests/py/test_identity_pages.py
@@ -33,11 +33,11 @@ def verify(self, username, *codes):
 
     # il - identities listing
 
-    def test_il_is_403_for_anon(self):
-        assert self.client.GxT('/~bob/identities/').code == 403
+    def test_il_is_401_for_anon(self):
+        assert self.client.GxT('/~bob/identities/').code == 401
 
-    def test_il_is_403_for_non_admin(self):
-        assert self.client.GxT('/~bob/identities/').code == 403
+    def test_il_is_401_for_non_admin(self):
+        assert self.client.GxT('/~bob/identities/').code == 401
 
     def test_il_is_200_for_self(self):
         assert self.client.GET('/~bob/identities/', auth_as='bob').code == 200
@@ -51,11 +51,11 @@ def test_il_is_200_for_admin(self):
     def test_ip_disallows_methods(self):
         assert self.client.hxt('HEAD', '/~bob/identities/TT').code == 405
 
-    def test_ip_is_403_for_anon(self):
-        assert self.client.GxT('/~bob/identities/TT').code == 403
+    def test_ip_is_401_for_anon(self):
+        assert self.client.GxT('/~bob/identities/TT').code == 401
 
-    def test_ip_is_403_for_non_admin(self):
-        assert self.client.GxT('/~bob/identities/TT').code == 403
+    def test_ip_is_401_for_non_admin(self):
+        assert self.client.GxT('/~bob/identities/TT').code == 401
 
     def test_ip_is_200_for_self(self):
         assert self.client.GET('/~bob/identities/TT', auth_as='bob').code == 200
diff --git a/tests/py/test_statement_json.py b/tests/py/test_statement_json.py
--- a/tests/py/test_statement_json.py
+++ b/tests/py/test_statement_json.py
@@ -23,13 +23,13 @@ def test_participant_can_change_their_statement(self):
         actual = json.loads(response.body)['html']
         assert actual == '<p>Lorem ipsum</p>\n'
 
-    def test_anonymous_gets_403(self):
+    def test_anonymous_gets_401(self):
         response = self.change_statement( 'en'
                                         , 'Some statement'
                                         , auth_as=None
                                         , expecting_error=True
                                          )
-        assert response.code == 403, response.code
+        assert response.code == 401, response.code
 
     def test_participant_cannot_change_their_statement_if_too_big(self):
         too_big_statement = 'A' * ((256 * 1024) + 1)
diff --git a/tests/py/test_username_json.py b/tests/py/test_username_json.py
--- a/tests/py/test_username_json.py
+++ b/tests/py/test_username_json.py
@@ -21,9 +21,9 @@ def test_participant_can_change_their_username(self):
         assert code == 200
         assert body['username'] == "bob"
 
-    def test_anonymous_gets_403(self):
+    def test_anonymous_gets_401(self):
         code, body = self.change_username("bob", auth_as=None)
-        assert code == 403
+        assert code == 401
 
     def test_empty(self):
         code, body = self.change_username('      ')