Repository: umuttopalak/pass-cli

Issue:
Master Password
Code still wants master password while retrieving passwords

Discussion:

Solution patch:
diff --git a/pass_cli/commands/generate.py b/pass_cli/commands/generate.py
index a7234d6..e3deda1 100644
--- a/pass_cli/commands/generate.py
+++ b/pass_cli/commands/generate.py
@@ -12,44 +12,39 @@
 @click.option('--no-copy', is_flag=True, help='Show password in terminal instead of copying to clipboard')
 def generate(length: int, service: str, username: str, no_copy: bool) -> None:
     """Generate a secure random password"""
-    if not check_sudo():
-        click.echo(click.style("✗ Authentication required! Please run 'pass-cli auth' first.", fg="red"))
-        raise click.Abort()
+    if service and not check_sudo():
+        click.echo(click.style(
+            "✗ Authentication required! Please run 'pass-cli auth' first.", fg="red"))
+        return
 
-    if length < 8:
-        click.echo(click.style("Warning: Password length should be at least 8 characters", fg="yellow"))
+    if service and not check_initialized():
+        click.echo(click.style(
+            "✗ Password manager not initialized! Please run 'pass-cli init' first.", 
+            fg="red"))
         return
 
-    final_password = generate_strong_password(length=length)
-
-    if service and username:
-        try:
-            if not check_initialized():
-                click.echo(click.style("Password manager not initialized. Please run 'pass-cli init' first.", fg="red"))
-                raise click.Abort()
-
-            stored_key = PasswordManager.get_stored_key()
-            if not stored_key:
-                encryption_key = click.prompt('Enter encryption key', hide_input=True)
-            else:
-                encryption_key = stored_key
-
-            try:
-                pm = PasswordManager(encryption_key)
-                pm.store_password(service, username, final_password)
-                click.echo(click.style(f"Generated and stored password for {service}", fg="green"))
-            except ValueError:
-                click.echo(click.style("Invalid encryption key", fg="red"))
-                raise click.Abort()
-        except Exception as e:
-            click.echo(click.style(f"Error storing password: {str(e)}", fg="red"))
-            raise click.Abort()
-
-    if no_copy:
-        # Show password in terminal if --no-copy is used
-        click.echo(click.style("Generated password:", fg="green"))
-        click.echo(final_password)
-    else:
-        # Only copy to clipboard by default
-        pyperclip.copy(final_password)
-        click.echo(click.style("✓ Password copied to clipboard!", fg="green"))
+    try:
+        password = generate_strong_password(length)
+
+        if service and username:
+            encryption_key = PasswordManager.get_stored_key()
+            if not encryption_key:
+                click.echo(click.style(
+                    "✗ No encryption key found! Please run 'pass-cli init' first.", fg="red"))
+                return
+
+            password_manager = PasswordManager(encryption_key)
+            password_manager.store_password(service, username, password)
+            click.echo(click.style(
+                f"✓ Generated and stored password for {service}", fg="green"))
+
+        if no_copy:
+            click.echo(click.style("Generated password:", fg="green"))
+            click.echo(password)
+        else:
+            pyperclip.copy(password)
+            click.echo(click.style("✓ Password copied to clipboard!", fg="green"))
+
+    except Exception as e:
+        click.echo(click.style(f"✗ {str(e)}", fg="red"))
+        return
diff --git a/pass_cli/commands/init.py b/pass_cli/commands/init.py
index b510c82..64623b0 100644
--- a/pass_cli/commands/init.py
+++ b/pass_cli/commands/init.py
@@ -4,36 +4,41 @@
 import click
 
 from ..database import PasswordManager
-from ..utils import generate_strong_password
+from ..utils import check_initialized, generate_strong_password
 
 
 @click.command()
 def init():
-    """Initialize the password manager with an encryption key.
-    
-    If no key is entered, a secure random key will be generated.
-    Make sure to save your encryption key - it cannot be recovered!
-    """
+    """Initialize the password manager"""
     try:
-        if PasswordManager()._has_encryption_key():
-            click.echo(click.style("Password manager is already initialized", fg="yellow"))
+        if check_initialized():
+            click.echo(click.style(
+                "✗ Password manager is already initialized!", fg="yellow"))
             return
 
-        click.echo("Enter encryption key (press Enter for a random secure key)")
-        key = click.prompt('Encryption key', hide_input=True, default='', show_default=False)
-        
-        if not key:
-            key = generate_strong_password(is_encryption_key=True)
-            click.echo(click.style("\nGenerated encryption key:", fg="green"))
-            click.echo(click.style(key, fg="yellow"))
-            click.echo(click.style("\n⚠️  IMPORTANT: Save this encryption key securely! You won't be able to recover it!", fg="red"))
-        else:
-            confirm = click.prompt('Confirm encryption key', hide_input=True)
-            if key != confirm:
-                click.echo(click.style("Error: Keys do not match", fg="red"))
-                raise click.Abort()
-        
-        pm = PasswordManager(key)
-        click.echo(click.style("\nPassword manager initialized successfully", fg="green"))
+        encryption_key = click.prompt(
+            "Enter encryption key (or press Enter to generate one)", 
+            default='', 
+            hide_input=True
+        )
+
+        if not encryption_key:
+            encryption_key = generate_strong_password(is_encryption_key=True)
+            click.echo(click.style(
+                "Generated secure encryption key.", fg="green"))
+
+        confirm_key = click.prompt(
+            "Confirm encryption key", 
+            hide_input=True
+        )
+
+        if encryption_key != confirm_key:
+            click.echo(click.style("✗ Encryption keys do not match!", fg="red"))
+            return
+
+        PasswordManager(encryption_key)
+        click.echo(click.style("✓ Password manager initialized successfully!", fg="green"))
+
     except Exception as e:
-        click.echo(click.style(f"Error initializing password manager: {str(e)}", fg="red")) 
\ No newline at end of file
+        click.echo(click.style(f"✗ {str(e)}", fg="red"))
+        return 
\ No newline at end of file
diff --git a/pass_cli/commands/list.py b/pass_cli/commands/list.py
index efaeb7b..0031e15 100644
--- a/pass_cli/commands/list.py
+++ b/pass_cli/commands/list.py
@@ -10,39 +10,42 @@
 @click.option('--service', '-s', help='Filter passwords by service name')
 def list(service: str = None) -> None:
     """List saved passwords for all or specific service"""
-    try:
-        if not check_sudo():
-            click.echo(click.style(
-                "✗ Authentication required! Please run 'pass-cli auth' first.", fg="red"))
-            sys.exit(1)
+    if not check_sudo():
+        click.echo(click.style(
+            "✗ Authentication required! Please run 'pass-cli auth' first.", fg="red"))
+        return
 
-        if not check_initialized():
+    if not check_initialized():
+        click.echo(click.style(
+            "✗ Password manager not initialized! Please run 'pass-cli init' first.", 
+            fg="red"))
+        return
+
+    try:
+        encryption_key = PasswordManager.get_stored_key()
+        if not encryption_key:
             click.echo(click.style(
-                "✗ Password manager not initialized! Please run 'pass-cli init' first.", 
-                fg="red"))
-            raise click.Abort()
+                "✗ No encryption key found! Please run 'pass-cli init' first.", fg="red"))
+            return
 
-        # Get encryption key and initialize password manager
-        password_manager = PasswordManager(PasswordManager.get_stored_key())
-        
-        # Get stored passwords
+        password_manager = PasswordManager(encryption_key)
         stored_passwords = password_manager.list_passwords(service)
         
         if not stored_passwords:
             if service:
                 click.echo(click.style(
-                    f"No passwords found for service: {service}", fg="yellow"))
+                    f"✗ No passwords found for service: {service}", fg="yellow"))
             else:
-                click.echo(click.style("No passwords stored yet.", fg="yellow"))
+                click.echo(click.style("✗ No passwords stored yet.", fg="yellow"))
             return
 
-        # Display results
         click.echo(click.style("\nStored Passwords:", fg="green"))
         click.echo("─" * 50)
         for service_name, username in stored_passwords:
             click.echo(f"Service: {service_name}")
             click.echo(f"Username: {username}")
             click.echo("─" * 50)
+
     except Exception as e:
-        click.echo(click.style(f"Error: {str(e)}", fg="red"))
-        raise click.Abort()
+        click.echo(click.style(f"✗ {str(e)}", fg="red"))
+        return
diff --git a/pass_cli/commands/retrieve.py b/pass_cli/commands/retrieve.py
index 1aac1ed..0b21bee 100644
--- a/pass_cli/commands/retrieve.py
+++ b/pass_cli/commands/retrieve.py
@@ -1,31 +1,49 @@
-import os
-
 import click
+import pyperclip
 
 from ..database import PasswordManager
-from ..utils import check_sudo
+from ..utils import check_initialized, check_sudo
 
 
 @click.command()
 @click.option('--service', '-s', required=True, help='Service name')
 @click.option('--username', '-u', required=True, help='Username')
-def retrieve(service: str, username: str):
-    """Retrieve a password for a service"""
+@click.option('--no-copy', is_flag=True, help='Show password in terminal instead of copying to clipboard')
+def retrieve(service: str, username: str, no_copy: bool) -> None:
+    """Retrieve a stored password"""
     if not check_sudo():
-        click.echo(click.style("✗ Authentication required! Please run 'pass-cli auth' first.", fg="red"))
-        raise click.Abort()
+        click.echo(click.style(
+            "✗ Authentication required! Please run 'pass-cli auth' first.", fg="red"))
+        return
+
+    if not check_initialized():
+        click.echo(click.style(
+            "✗ Password manager not initialized! Please run 'pass-cli init' first.", 
+            fg="red"))
+        return
 
     try:
-        master_password = click.prompt('Enter master password', hide_input=True)
-        pm = PasswordManager(master_password)
-        password = pm.get_password(service, username)
-        
-        if password:
-            click.echo(click.style(f"Password for {service}:", fg="green"))
+        encryption_key = PasswordManager.get_stored_key()
+        if not encryption_key:
+            click.echo(click.style(
+                "✗ No encryption key found! Please run 'pass-cli init' first.", fg="red"))
+            return
+
+        password_manager = PasswordManager(encryption_key)
+
+        password = password_manager.get_password(service, username)
+        if password is None:
+            click.echo(click.style(
+                f"✗ No password found for {service} / {username}", fg="red"))
+            return
+
+        if no_copy:
+            click.echo(click.style("Retrieved password:", fg="green"))
             click.echo(password)
         else:
-            click.echo(click.style(f"No password found for {service} with username {username}", fg="yellow"))
-    except ValueError as e:
-        click.echo(click.style(f"Error: {str(e)}", fg="red"))
+            pyperclip.copy(password)
+            click.echo(click.style("✓ Password copied to clipboard!", fg="green"))
+
     except Exception as e:
-        click.echo(click.style(f"Error retrieving password: {str(e)}", fg="red"))
+        click.echo(click.style(f"✗ {str(e)}", fg="red"))
+        return
diff --git a/pass_cli/commands/store.py b/pass_cli/commands/store.py
index 05b4990..54a3efa 100644
--- a/pass_cli/commands/store.py
+++ b/pass_cli/commands/store.py
@@ -3,25 +3,37 @@
 import click
 
 from ..database import PasswordManager
-from ..utils import check_sudo
+from ..utils import check_initialized, check_sudo
 
 
 @click.command()
 @click.option('--service', '-s', required=True, help='Service name')
 @click.option('--username', '-u', required=True, help='Username')
 @click.option('--password', '-p', required=True, help='Password to store')
-def store(service: str, username: str, password: str):
+def store(service: str, username: str, password: str) -> None:
     """Store a password for a service"""
     if not check_sudo():
-        click.echo(click.style("✗ Authentication required! Please run 'pass-cli auth' first.", fg="red"))
-        raise click.Abort()
+        click.echo(click.style(
+            "✗ Authentication required! Please run 'pass-cli auth' first.", fg="red"))
+        return
+
+    if not check_initialized():
+        click.echo(click.style(
+            "✗ Password manager not initialized! Please run 'pass-cli init' first.", 
+            fg="red"))
+        return
 
     try:
-        master_password = click.prompt('Enter master password', hide_input=True)
-        pm = PasswordManager(master_password)
-        pm.store_password(service, username, password)
-        click.echo(click.style(f"Password stored successfully for {service}", fg="green"))
-    except ValueError as e:
-        click.echo(click.style(f"Error: {str(e)}", fg="red"))
+        encryption_key = PasswordManager.get_stored_key()
+        if not encryption_key:
+            click.echo(click.style(
+                "✗ No encryption key found! Please run 'pass-cli init' first.", fg="red"))
+            return
+
+        password_manager = PasswordManager(encryption_key)
+        password_manager.store_password(service, username, password)
+        click.echo(click.style("✓ Password stored successfully!", fg="green"))
+
     except Exception as e:
-        click.echo(click.style(f"Error storing password: {str(e)}", fg="red"))
+        click.echo(click.style(f"✗ {str(e)}", fg="red"))
+        return


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
index 4783b53..da7923e 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -65,20 +65,16 @@ def test_generate_command(runner, mock_sudo, mock_keyring, mock_db_path):
         assert result.exit_code == 0
         password = result.output.strip().split('\n')[-1]
         assert len(password) == 16
-        assert any(c.islower() for c in password)
-        assert any(c.isupper() for c in password)
-        assert any(c.isdigit() for c in password)
-        assert any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password)
 
         # Test generation with storage
         result = runner.invoke(generate, [
             '--length', '16',
             '--service', 'testservice',
-            '--username', 'testuser'
+            '--username', 'testuser',
+            '--no-copy'
         ], input=mock_keyring['key'] + '\n')
         assert result.exit_code == 0
-        assert "Generated and stored password for testservice" in result.output
-        assert "✓ Password copied to clipboard!" in result.output
+        assert "Generated and stored password" in result.output
 
 
 def test_store_command(runner, mock_sudo, mock_keyring, mock_db_path):
@@ -92,8 +88,9 @@ def test_store_command(runner, mock_sudo, mock_keyring, mock_db_path):
             '--service', 'testservice',
             '--username', 'testuser',
             '--password', 'testpass'
-        ], input=mock_keyring['key'] + '\n')  # Provide encryption key as input
+        ], input=mock_keyring['key'] + '\n')
         assert result.exit_code == 0
+        assert "Password stored successfully" in result.output
 
 
 def test_retrieve_command(runner, mock_sudo, mock_keyring, mock_db_path):
@@ -108,14 +105,15 @@ def test_retrieve_command(runner, mock_sudo, mock_keyring, mock_db_path):
             '--service', 'testservice',
             '--username', 'testuser',
             '--password', 'testpass'
-        ], input=mock_keyring['key'] + '\n')  # Provide encryption key as input
+        ], input=mock_keyring['key'] + '\n')
         assert store_result.exit_code == 0
 
-        # Then retrieve it
+        # Then retrieve it with --no-copy flag to see the password
         result = runner.invoke(retrieve, [
             '--service', 'testservice',
-            '--username', 'testuser'
-        ], input=mock_keyring['key'] + '\n')  # Provide encryption key as input
+            '--username', 'testuser',
+            '--no-copy'  # Add this flag to show password in output
+        ], input=mock_keyring['key'] + '\n')
         assert result.exit_code == 0
         assert "testpass" in result.output
 
@@ -168,9 +166,8 @@ def test_list_passwords(runner, initialized_db):
 
 def test_list_passwords_no_auth(runner, initialized_db):
     """Test listing passwords without authentication"""
-    with patch('pass_cli.commands.list.check_sudo', return_value=False):  # Doğrudan list.py içindeki check_sudo'yu mock'la
+    with patch('pass_cli.commands.list.check_sudo', return_value=False):
         result = runner.invoke(list)
-        assert result.exit_code == 1  # click.Abort() 1 döndürür
         assert "Authentication required!" in result.output
 
 
@@ -178,5 +175,4 @@ def test_list_passwords_not_initialized(runner, mock_db_path):
     """Test listing passwords without initialization"""
     with runner.isolated_filesystem():
         result = runner.invoke(list)
-        assert result.exit_code != 0
         assert "Password manager not initialized!" in result.output