Repository: zulip/zulip-terminal

Issue:
On login, providing zulip server prefixed by http:// or https:// manifests in incorrect username/password error
On running zulip-term for the first time:
```
(zulip_terminal_pyvenv) [ahelwer@node ~]$ zulip-term
zuliprc file was not found at /home/ahelwer/zuliprc
Please enter your credentials to login into your Zulip organization.

NOTE: The Zulip URL is where you would go in a web browser to log in to Zulip.
It often looks like one of the following:
   your-org.zulipchat.com (Zulip cloud)
   zulip.your-org.com (self-hosted servers)
   chat.zulip.org (the Zulip community server)
Zulip URL: http://leanprover.zulipchat.com
Email: ahelwer@protonmail.com
Password: 

Incorrect Email(or Username) or Password!
```
I tried various times to paste in or manually type my password with the same result. Redoing things using `leanprover.zulipchat.com` instead of `http://leanprover.zulipchat.com` fixed things:
```
(zulip_terminal_pyvenv) [ahelwer@node ~]$ zulip-term
zuliprc file was not found at /home/ahelwer/zuliprc
Please enter your credentials to login into your Zulip organization.

NOTE: The Zulip URL is where you would go in a web browser to log in to Zulip.
It often looks like one of the following:
   your-org.zulipchat.com (Zulip cloud)
   zulip.your-org.com (self-hosted servers)
   chat.zulip.org (the Zulip community server)
Zulip URL: leanprover.zulipchat.com
Email: ahelwer@protonmail.com
Password: 
Generated API key saved at /home/ahelwer/zuliprc
...
```
Happy I managed to figure it out but it might be a good idea to improve the error message for usability!

Discussion:

Patch:
diff --git a/zulipterminal/cli/run.py b/zulipterminal/cli/run.py
index 8880ac0..225f37d 100755
--- a/zulipterminal/cli/run.py
+++ b/zulipterminal/cli/run.py
@@ -11,7 +11,7 @@ import sys
 import traceback
 from enum import Enum
 from os import path, remove
-from typing import Dict, List, NamedTuple, Optional, Tuple
+from typing import Any, Dict, List, NamedTuple, Optional, Tuple
 
 import requests
 from urwid import display_common, set_encoding
@@ -206,10 +206,9 @@ def styled_input(label: str) -> str:
     return input(in_color("blue", label))
 
 
-def get_login_id(realm_url: str) -> str:
-    res_json = requests.get(url=f"{realm_url}/api/v1/server_settings").json()
-    require_email_format_usernames = res_json["require_email_format_usernames"]
-    email_auth_enabled = res_json["email_auth_enabled"]
+def get_login_id(server_properties: Dict[str, Any]) -> str:
+    require_email_format_usernames = server_properties["require_email_format_usernames"]
+    email_auth_enabled = server_properties["email_auth_enabled"]
 
     if not require_email_format_usernames and email_auth_enabled:
         label = "Email or Username: "
@@ -222,20 +221,27 @@ def get_login_id(realm_url: str) -> str:
     return styled_input(label)
 
 
-def get_api_key(realm_url: str) -> Optional[Tuple[str, str]]:
+def get_api_key(realm_url: str) -> Optional[Tuple[str, str, str]]:
     from getpass import getpass
 
-    login_id = get_login_id(realm_url)
+    server_properties = requests.get(url=f"{realm_url}/api/v1/server_settings").json()
+
+    # Assuming we connect to and get data from the server, use the realm_url it suggests
+    # This avoids cases where there are redirects between http and https, for example
+    preferred_realm_url = server_properties["realm_uri"]
+
+    login_id = get_login_id(server_properties)
     password = getpass(in_color("blue", "Password: "))
+
     response = requests.post(
-        url=f"{realm_url}/api/v1/fetch_api_key",
+        url=f"{preferred_realm_url}/api/v1/fetch_api_key",
         data={
             "username": login_id,
             "password": password,
         },
     )
     if response.status_code == requests.codes.OK:
-        return login_id, str(response.json()["api_key"])
+        return preferred_realm_url, login_id, str(response.json()["api_key"])
     return None
 
 
@@ -265,12 +271,12 @@ def fetch_zuliprc(zuliprc_path: str) -> None:
         print(in_color("red", "\nIncorrect Email(or Username) or Password!\n"))
         login_data = get_api_key(realm_url)
 
-    login_id, api_key = login_data
+    preferred_realm_url, login_id, api_key = login_data
     save_zuliprc_failure = _write_zuliprc(
         zuliprc_path,
         login_id=login_id,
         api_key=api_key,
-        server_url=realm_url,
+        server_url=preferred_realm_url,
     )
     if not save_zuliprc_failure:
         print(f"Generated API key saved at {zuliprc_path}")


Test patch:
diff --git a/tests/cli/test_run.py b/tests/cli/test_run.py
index 7653425..9fafdab 100644
--- a/tests/cli/test_run.py
+++ b/tests/cli/test_run.py
@@ -55,16 +55,13 @@ def test_in_color(color: str, code: str, text: str = "some text") -> None:
     ],
 )
 def test_get_login_id(mocker: MockerFixture, json: Dict[str, bool], label: str) -> None:
-    response = mocker.Mock(json=lambda: json)
-    mocked_get = mocker.patch("requests.get", return_value=response)
     mocked_styled_input = mocker.patch(
         MODULE + ".styled_input", return_value="input return value"
     )
 
-    result = get_login_id("REALM_URL")
+    result = get_login_id(json)
 
     assert result == "input return value"
-    mocked_get.assert_called_with(url="REALM_URL/api/v1/server_settings")
     mocked_styled_input.assert_called_with(label + ": ")
 
 
@@ -328,7 +325,9 @@ def test_main_cannot_write_zuliprc_given_good_credentials(
 
     # Give some arbitrary input and fake that it's always valid
     mocker.patch.object(builtins, "input", lambda _: "text\n")
-    mocker.patch(MODULE + ".get_api_key", return_value=("my_login", "my_api_key"))
+    mocker.patch(
+        MODULE + ".get_api_key", return_value=("my_site", "my_login", "my_api_key")
+    )
 
     with pytest.raises(SystemExit):
         main([])