Repository: fractal-analytics-platform/fractal-client (language: python)

Issue:
Improve CLI authentication
These are some thoughts from today's Fractal call on topics for a CLI authentication refactor:

The pain-points:
- It's tricky when switching between users with the caching (2 on-disk files: .fractal.env file & the token file in the cache => create a new .fractal.env file, but the existing token is still used)
- We still require a .fractal.env file for the server IP, right? Thus, there isn't a way to do things without that env file for the time being
- Storing passwords on disk in .fractal.env files is not a good way forward. Great for testing obviously, but we should move away from it
- Not super intuitive for onboarding people that don't run their own setup scripts: When does the .fractal.env file get created? Why can't I use the command-line client from a different folder?


Goals: 
1. No passwords on disk
2. Still easy to use and easy for testing

---

Some ideas:

- The fractal-demos scripts could ask interactively for username, password & IP/port (maybe with a default that we run the "production" server on) and use those on all fractal client commands afterwards.
- What's a good way to not have to type -u username -p password for every fractal client command? Some token or hashed storage, but when the user provides -u username -p password those get overwritten? In that case, only the first command in a demo script would need a -u -p, the rest would still use a (more securely) cached login.


Discussion / hints:

Fix patch:
diff --git a/fractal_client/cmd/_user.py b/fractal_client/cmd/_user.py
index 269f45ef8..b03745f5f 100644
--- a/fractal_client/cmd/_user.py
+++ b/fractal_client/cmd/_user.py
@@ -10,7 +10,7 @@ def user_register(
     client: AuthClient,
     *,
     new_email: str,
-    new_password: Optional[str] = None,
+    new_password: str,
     slurm_user: Optional[str] = None,
     cache_dir: Optional[str] = None,
     username: Optional[str] = None,
@@ -30,16 +30,6 @@ def user_register(
     if username:
         new_user["username"] = username
 
-    from getpass import getpass
-
-    if new_password is None:
-        new_password = getpass()
-        confirm_new_password = getpass("Confirm password: ")
-        if new_password == confirm_new_password:
-            new_user["password"] = new_password
-        else:
-            return Interface(retcode=1, data="Passwords do not match.")
-
     res = client.post(
         f"{settings.FRACTAL_SERVER}/auth/register/", json=new_user
     )
diff --git a/fractal_client/cmd/_workflow.py b/fractal_client/cmd/_workflow.py
index 15987a71a..034115379 100644
--- a/fractal_client/cmd/_workflow.py
+++ b/fractal_client/cmd/_workflow.py
@@ -247,10 +247,6 @@ def workflow_import(
             "importing this workflow may not work as expected."
         )
 
-    from devtools import debug
-
-    debug(batch)
-
     if batch:
         datastr = f"{wf_read['id']}"
         for wftask in wf_read["task_list"]:


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index 44af3dfb9..da800389a 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -12,6 +12,12 @@
 DEFAULT_TEST_EMAIL = environ["FRACTAL_USER"]
 
 
+def test_debug(invoke):
+    res = invoke("--debug version")
+    assert res.retcode == 0
+    debug(res.data)
+
+
 def test_version(invoke):
     iface = invoke("version")
     debug(iface.data)
@@ -78,6 +84,20 @@ def test_missing_credentials(override_settings):
     assert res.retcode == 1
 
 
+def test_missing_password(override_settings):
+    """
+    GIVEN an invocation with missing password
+    THEN the client raises a MissingCredentialsError
+    """
+    # Remove password from settings
+    override_settings(FRACTAL_USER="some_user", FRACTAL_PASSWORD=None)
+
+    res = handle(shlex.split("fractal user whoami"))
+    debug(res.data)
+    assert res.retcode == 1
+    assert "FRACTAL_PASSWORD variable not defined" in res.data
+
+
 def test_connecterror(client, override_settings):
     override_settings(
         FRACTAL_USER="admin@fractal.xy",
diff --git a/tests/test_no_commands.py b/tests/test_no_commands.py
new file mode 100644
index 000000000..95c2b8207
--- /dev/null
+++ b/tests/test_no_commands.py
@@ -0,0 +1,15 @@
+import pytest
+
+
+def test_no_commands(invoke):
+    for command in [
+        "",
+        "dataset",
+        "project",
+        "job",
+        "workflow",
+        "task",
+        "user",
+    ]:
+        with pytest.raises(SystemExit):
+            invoke(command)