Repository: zulip/zulip

Issue:
"Log in to your organization" page should always take user to login page
At present, entering an organization with [public access](https://zulip.com/help/public-access-option) enabled into https://zulip.com/accounts/go/ takes the user to the logged out view of that page.

However, it's much more likely that the user in this situation wants to log in, not to see the logged-out view. Thus, clicking "Next" should always go to the login page (`/login`), never the public view.

[CZO discussion](https://chat.zulip.org/#narrow/stream/101-design/topic/log.20in.20flow.20w.2F.20web-public/near/1454922)


Discussion:
My [previous PR](https://github.com/zulip/zulip/pull/23225) is awaiting **Integration Review**. In the meantime, I am working on this issue. Thanks.
Sure, assigning this issue to you -- thanks @prakhar1144 !

Resolving patch:
diff --git a/zerver/views/auth.py b/zerver/views/auth.py
--- a/zerver/views/auth.py
+++ b/zerver/views/auth.py
@@ -796,9 +796,11 @@ def login_page(
     is_preview = "preview" in request.GET
     if settings.TWO_FACTOR_AUTHENTICATION_ENABLED:
         if request.user.is_authenticated and is_2fa_verified(request.user):
-            return HttpResponseRedirect(request.user.realm.uri)
+            redirect_to = get_safe_redirect_to(next, request.user.realm.uri)
+            return HttpResponseRedirect(redirect_to)
     elif request.user.is_authenticated and not is_preview:
-        return HttpResponseRedirect(request.user.realm.uri)
+        redirect_to = get_safe_redirect_to(next, request.user.realm.uri)
+        return HttpResponseRedirect(redirect_to)
     if is_subdomain_root_or_alias(request) and settings.ROOT_DOMAIN_LANDING_PAGE:
         redirect_url = reverse("realm_redirect")
         if request.GET:
diff --git a/zerver/views/registration.py b/zerver/views/registration.py
--- a/zerver/views/registration.py
+++ b/zerver/views/registration.py
@@ -2,10 +2,10 @@
 import urllib
 from contextlib import suppress
 from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
-from urllib.parse import urlencode
+from urllib.parse import urlencode, urljoin
 
 from django.conf import settings
-from django.contrib.auth import authenticate, get_backends
+from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, get_backends
 from django.contrib.sessions.backends.base import SessionBase
 from django.core import validators
 from django.core.exceptions import ValidationError
@@ -83,7 +83,6 @@
     create_preregistration_user,
     finish_desktop_flow,
     finish_mobile_flow,
-    get_safe_redirect_to,
     redirect_and_log_into_subdomain,
     redirect_to_deactivation_notice,
 )
@@ -988,7 +987,13 @@ def realm_redirect(request: HttpRequest, next: str = REQ(default="")) -> HttpRes
         if form.is_valid():
             subdomain = form.cleaned_data["subdomain"]
             realm = get_realm(subdomain)
-            redirect_to = get_safe_redirect_to(next, realm.uri)
+            redirect_to = urljoin(realm.uri, settings.HOME_NOT_LOGGED_IN)
+
+            if next:
+                redirect_to = append_url_query_string(
+                    redirect_to, urlencode({REDIRECT_FIELD_NAME: next})
+                )
+
             return HttpResponseRedirect(redirect_to)
     else:
         form = RealmRedirectForm()


Test patch:
diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py
--- a/zerver/tests/test_auth_backends.py
+++ b/zerver/tests/test_auth_backends.py
@@ -420,7 +420,7 @@ def test_login_preview(self) -> None:
 
         result = self.client_get("/login/")
         self.assertEqual(result.status_code, 302)
-        self.assertEqual(result["Location"], "http://zulip.testserver")
+        self.assertEqual(result["Location"], "http://zulip.testserver/")
 
     @override_settings(AUTHENTICATION_BACKENDS=("zproject.backends.ZulipDummyBackend",))
     def test_no_backend_enabled(self) -> None:
@@ -4988,7 +4988,7 @@ def totp(*args: Any, **kwargs: Any) -> int:
             # already logged in.
             result = self.client_get("/accounts/login/")
             self.assertEqual(result.status_code, 302)
-            self.assertEqual(result["Location"], "http://zulip.testserver")
+            self.assertEqual(result["Location"], "http://zulip.testserver/")
 
 
 class TestDevAuthBackend(ZulipTestCase):
diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py
--- a/zerver/tests/test_signup.py
+++ b/zerver/tests/test_signup.py
@@ -1049,7 +1049,7 @@ def test_login_page_redirects_logged_in_user(self) -> None:
         """
         self.login("cordelia")
         response = self.client_get("/login/")
-        self.assertEqual(response["Location"], "http://zulip.testserver")
+        self.assertEqual(response["Location"], "http://zulip.testserver/")
 
     def test_options_request_to_login_page(self) -> None:
         response = self.client_options("/login/")
@@ -1067,7 +1067,7 @@ def test_login_page_redirects_logged_in_user_under_2fa(self) -> None:
         self.login_2fa(user_profile)
 
         response = self.client_get("/login/")
-        self.assertEqual(response["Location"], "http://zulip.testserver")
+        self.assertEqual(response["Location"], "http://zulip.testserver/")
 
     def test_start_two_factor_auth(self) -> None:
         request = HostRequestMock()
@@ -3962,6 +3962,14 @@ def test_login_page_redirects_for_root_domain(self, mock_get_host: MagicMock) ->
             self.assertEqual(result.status_code, 302)
             self.assertEqual(result["Location"], "/accounts/go/?next=%2Fupgrade%2F")
 
+    def test_login_page_redirects_using_next_when_already_authenticated(self) -> None:
+        hamlet = self.example_user("hamlet")
+        self.login("hamlet")
+
+        result = self.client_get("/login/", {"next": "/upgrade/"})
+        self.assertEqual(result.status_code, 302)
+        self.assertEqual(result["Location"], f"{hamlet.realm.uri}/upgrade/")
+
     @patch("django.http.HttpRequest.get_host")
     def test_login_page_works_without_subdomains(self, mock_get_host: MagicMock) -> None:
         mock_get_host.return_value = "www.testserver"
@@ -4227,7 +4235,7 @@ def totp(*args: Any, **kwargs: Any) -> int:
             # Going to login page should redirect to '/' if user is already
             # logged in.
             result = self.client_get("/accounts/login/")
-            self.assertEqual(result["Location"], "http://zulip.testserver")
+            self.assertEqual(result["Location"], "http://zulip.testserver/")
 
 
 class NameRestrictionsTest(ZulipTestCase):
@@ -4242,7 +4250,7 @@ def test_realm_redirect_without_next_param(self) -> None:
 
         result = self.client_post("/accounts/go/", {"subdomain": "zephyr"})
         self.assertEqual(result.status_code, 302)
-        self.assertEqual(result["Location"], "http://zephyr.testserver")
+        self.assertEqual(result["Location"], "http://zephyr.testserver/login/")
 
         result = self.client_post("/accounts/go/", {"subdomain": "invalid"})
         self.assert_in_success_response(["We couldn&#39;t find that Zulip organization."], result)
@@ -4255,4 +4263,4 @@ def test_realm_redirect_with_next_param(self) -> None:
 
         result = self.client_post("/accounts/go/?next=billing", {"subdomain": "lear"})
         self.assertEqual(result.status_code, 302)
-        self.assertEqual(result["Location"], "http://lear.testserver/billing")
+        self.assertEqual(result["Location"], "http://lear.testserver/login/?next=billing")