Repository: oppia/oppia

Issue:
Opening a new tab while in signup page causes session to expire.
So, I goto the signup page on dev and in another tab open the splash/library page and when I come back to the signup page and type some username it gives the following error:

https://files.gitter.im/oppia/oppia-chat/blob

Is this intended workflow?

Opening a new tab while in signup page causes session to expire.
So, I goto the signup page on dev and in another tab open the splash/library page and when I come back to the signup page and type some username it gives the following error:

https://files.gitter.im/oppia/oppia-chat/blob

Is this intended workflow?



Discussion:
This definitely doesn't look like a behavior that is normal, perhaps @seanlip, can chime in a bit more about this. 

@kevinlee12 can you repro this on develop?

@seanlip, I am able to reproduce the error. Specifically the error is:

Your session has expired, and unfortunately your changes cannot be saved.
Please refresh the page.

The error was most reliably reproduced when I did a open in new tab when
clicking on the Oppia logo in the upper left corner, I haven't run into
this scenario before (opening another Oppia tab while on the sign-in page)
so I can't comment if this is a recent issue.

On Wed, Jul 27, 2016 at 7:52 PM, Sean Lip notifications@github.com wrote:

> @kevinlee12 https://github.com/kevinlee12 can you repro this on develop?
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> https://github.com/oppia/oppia/issues/2306#issuecomment-235786609, or mute
> the thread
> https://github.com/notifications/unsubscribe-auth/AFfdMKHyd3CopsDT7Yll46fcuA9dcplCks5qaBlTgaJpZM4JWDQR
> .

also adding that this issue extends back to v2.3.0 

Ah, I know what's going on here. What should happen is the following: if you've opened another page in another tab, that resets your sign-in status; when you try to enter and save a username, the error message should be "It looks like you've signed out in another tab. You'll need to sign back in." or similar.

To fix this, here's what to do:
- In the backend, when the username comes in, check whether the user is signed in (i.e. if self.user_id is not None). If so, follow the existing flow. If not, find a way to tell the frontend "the user is not signed in". The frontend should handle this error correctly and show the above warning message to the user.

Also, /cc @jaredsilver @jblair87 to advise on what the exact text of the message should be.
We could copy Google word-for-word here:
> Sorry, you have been logged out [probably in another window]. Please log in again.
I wonder also if this interaction might be somehow resolved by an implementation of #2969 (in which case navigating to a new page might theoretically persist a temporary user ID).
I think this is separate, actually -- we do want people to get signed out in this case, rather than end up in some kind of partially-logged-in state.
I agree with @jaredsilver re the text
@SudhanvaMG -- I think this is reasonably straightforward. Want to try it?
@seanlip Yes , i will work on this .
@seanlip I am finished with the redirecting the page back to splash after 5 seconds .
Feel free to submit a PR when you're ready. Please assign @kevinlee12 as reviewer.
@seanlip Ok i will.
(Not sure how this got auto-closed. Reopening.)
Will submit a PR  .

Some notes (from a review of #3030):
- The main thing to check in the backend is whether the user is logged in or not, i.e. whether self.user_id is None or not.
- If the user is logged out, send some kind of response to the frontend that the frontend can read and use to deduce that the user has logged out.
- It's possible that the http POST interceptors in app.js might be a good place to start -- we could intercept the backend response there and call some function in alertsService to show the blocking modal. (This would need to be a new function, separate from addWarning.)
@bansalnitish any updates on this issue?
Hi @aks681, working on it, will submit a PR by tomorrow. Thanks!
This definitely doesn't look like a behavior that is normal, perhaps @seanlip, can chime in a bit more about this. 

@kevinlee12 can you repro this on develop?

@seanlip, I am able to reproduce the error. Specifically the error is:

Your session has expired, and unfortunately your changes cannot be saved.
Please refresh the page.

The error was most reliably reproduced when I did a open in new tab when
clicking on the Oppia logo in the upper left corner, I haven't run into
this scenario before (opening another Oppia tab while on the sign-in page)
so I can't comment if this is a recent issue.

On Wed, Jul 27, 2016 at 7:52 PM, Sean Lip notifications@github.com wrote:

> @kevinlee12 https://github.com/kevinlee12 can you repro this on develop?
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> https://github.com/oppia/oppia/issues/2306#issuecomment-235786609, or mute
> the thread
> https://github.com/notifications/unsubscribe-auth/AFfdMKHyd3CopsDT7Yll46fcuA9dcplCks5qaBlTgaJpZM4JWDQR
> .

also adding that this issue extends back to v2.3.0 

Ah, I know what's going on here. What should happen is the following: if you've opened another page in another tab, that resets your sign-in status; when you try to enter and save a username, the error message should be "It looks like you've signed out in another tab. You'll need to sign back in." or similar.

To fix this, here's what to do:
- In the backend, when the username comes in, check whether the user is signed in (i.e. if self.user_id is not None). If so, follow the existing flow. If not, find a way to tell the frontend "the user is not signed in". The frontend should handle this error correctly and show the above warning message to the user.

Also, /cc @jaredsilver @jblair87 to advise on what the exact text of the message should be.
We could copy Google word-for-word here:
> Sorry, you have been logged out [probably in another window]. Please log in again.
I wonder also if this interaction might be somehow resolved by an implementation of #2969 (in which case navigating to a new page might theoretically persist a temporary user ID).
I think this is separate, actually -- we do want people to get signed out in this case, rather than end up in some kind of partially-logged-in state.
I agree with @jaredsilver re the text
@SudhanvaMG -- I think this is reasonably straightforward. Want to try it?
@seanlip Yes , i will work on this .
@seanlip I am finished with the redirecting the page back to splash after 5 seconds .
Feel free to submit a PR when you're ready. Please assign @kevinlee12 as reviewer.
@seanlip Ok i will.
(Not sure how this got auto-closed. Reopening.)
Will submit a PR  .

Some notes (from a review of #3030):
- The main thing to check in the backend is whether the user is logged in or not, i.e. whether self.user_id is None or not.
- If the user is logged out, send some kind of response to the frontend that the frontend can read and use to deduce that the user has logged out.
- It's possible that the http POST interceptors in app.js might be a good place to start -- we could intercept the backend response there and call some function in alertsService to show the blocking modal. (This would need to be a new function, separate from addWarning.)
@bansalnitish any updates on this issue?
Hi @aks681, working on it, will submit a PR by tomorrow. Thanks!

Resolving patch:
diff --git a/core/controllers/base.py b/core/controllers/base.py
--- a/core/controllers/base.py
+++ b/core/controllers/base.py
@@ -230,6 +230,15 @@ def dispatch(self):
 
         if self.payload is not None and self.REQUIRE_PAYLOAD_CSRF_CHECK:
             try:
+                # If user opens a new tab during signup process, the user_id
+                # parameter is set to None and this causes the signup session
+                # to expire. The code here checks if user is on the signup
+                # page and the user_id is None, if that is the case an exception
+                # is raised which is handled by the frontend by showing a
+                # continue to registration modal.
+                if 'signup' in self.request.uri and not self.user_id:
+                    raise self.UnauthorizedUserException(
+                        'Registration session expired.')
                 csrf_token = self.request.get('csrf_token')
                 if not csrf_token:
                     raise Exception(


Test patch:
diff --git a/core/controllers/base_test.py b/core/controllers/base_test.py
--- a/core/controllers/base_test.py
+++ b/core/controllers/base_test.py
@@ -697,3 +697,44 @@ def test_controller_class_names(self):
                                         msg=error_message)
 
         self.assertGreater(num_handlers_checked, 150)
+
+
+class SignUpTests(test_utils.GenericTestBase):
+
+    def test_error_is_raised_on_opening_new_tab_during_signup(self):
+        """Test that error is raised if user opens a new tab
+        during signup.
+        """
+        self.login('abc@example.com')
+        response = self.get_html_response(feconf.SIGNUP_URL)
+        csrf_token = self.get_csrf_token_from_response(response)
+
+        response = self.get_html_response('/about', expected_status_int=302)
+        self.assertIn('Logout', response.location)
+        self.logout()
+
+        response = self.post_json(
+            feconf.SIGNUP_DATA_URL, {
+                'username': 'abc',
+                'agreed_to_terms': True
+            }, csrf_token=csrf_token, expected_status_int=401,
+        )
+
+        self.assertEqual(response['error'], 'Registration session expired.')
+
+    def test_no_error_is_raised_on_opening_new_tab_after_signup(self):
+        """Test that no error is raised if user opens a new tab
+        after signup.
+        """
+        self.login('abc@example.com')
+        response = self.get_html_response(feconf.SIGNUP_URL)
+        csrf_token = self.get_csrf_token_from_response(response)
+
+        self.post_json(
+            feconf.SIGNUP_DATA_URL, {
+                'username': 'abc',
+                'agreed_to_terms': True
+            }, csrf_token=csrf_token,
+        )
+
+        self.get_html_response('/about')