From 19c5f0fea76ab147a4a46af52631eb6e57471d99 Mon Sep 17 00:00:00 2001 From: The Prime Mathematician <197593373+Prime-Lasking@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:01:56 +1100 Subject: [PATCH] Implement service name normalization Added normalization for service names to avoid duplicates. --- pryva/cli.py | 86 +++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/pryva/cli.py b/pryva/cli.py index 81179a0..b48abac 100644 --- a/pryva/cli.py +++ b/pryva/cli.py @@ -11,6 +11,11 @@ storage = PasswordStorage() +def normalize_service(service: str) -> str: + """Normalize service names to avoid duplicates.""" + return service.strip().lower() + + def get_master_password(prompt: str = "Master password: ") -> str: """Securely get master password from user.""" return getpass.getpass(prompt) @@ -36,23 +41,23 @@ def init(): if storage.is_initialized(): click.echo("Password vault is already initialized.") return - + click.echo("Initializing password vault...") click.echo("Choose a strong master password. This will be used to encrypt all your stored passwords.") - + while True: master_password = get_master_password("Create master password: ") if len(master_password) < 8: click.echo("Master password must be at least 8 characters long.") continue - + confirm_password = get_master_password("Confirm master password: ") if master_password != confirm_password: click.echo("Passwords don't match. Please try again.") continue - + break - + if storage.initialize_vault(master_password): click.echo("✓ Password vault initialized successfully!") click.echo("You can now add passwords with 'pryva add '") @@ -64,19 +69,19 @@ def init(): @click.argument('service') def add(service: str): """Add a new password entry for a service.""" + service = normalize_service(service) ensure_vault_initialized() - - # Check if service already exists + master_password = get_master_password() existing = storage.get_password(service, master_password) if existing: click.echo(f"Password for '{service}' already exists. Use 'pryva update {service}' to modify it.") return - + username = click.prompt("Username/Email", default="", show_default=False) password = getpass.getpass("Password: ") notes = click.prompt("Notes (optional)", default="", show_default=False) - + try: if storage.add_password(service, username, password, notes, master_password): click.echo(f"✓ Password for '{service}' added successfully!") @@ -91,36 +96,38 @@ def add(service: str): @click.option('--copy', '-c', is_flag=True, help='Copy password to clipboard') def get(service: str, copy: bool): """Retrieve password for a service.""" + service = normalize_service(service) ensure_vault_initialized() - + master_password = get_master_password() - + try: entry = storage.get_password(service, master_password) if not entry: click.echo(f"No password found for '{service}'.") return - + click.echo(f"\nService: {entry['service']}") click.echo(f"Username: {entry['username']}") - + if copy: try: import pyperclip pyperclip.copy(entry['password']) click.echo("Password: [copied to clipboard]") + click.echo("⚠ Clipboard contains your password until overwritten.") except ImportError: click.echo("pyperclip not installed. Install with 'pip install pyperclip' for clipboard support.") click.echo(f"Password: {entry['password']}") else: click.echo(f"Password: {entry['password']}") - + if entry['notes']: click.echo(f"Notes: {entry['notes']}") - + click.echo(f"Created: {entry['created_at']}") click.echo(f"Updated: {entry['updated_at']}") - + except ValueError as e: click.echo(f"✗ Error: {e}") @@ -129,12 +136,12 @@ def get(service: str, copy: bool): def list(): """List all stored services.""" ensure_vault_initialized() - + services = storage.list_services() if not services: click.echo("No passwords stored yet.") return - + click.echo(f"\nStored passwords ({len(services)} total):") for service in services: click.echo(f" • {service}") @@ -144,38 +151,34 @@ def list(): @click.argument('service') def update(service: str): """Update an existing password entry.""" + service = normalize_service(service) ensure_vault_initialized() - + master_password = get_master_password() - + try: - # Check if service exists existing = storage.get_password(service, master_password) if not existing: click.echo(f"No password found for '{service}'. Use 'pryva add {service}' to create it.") return - + click.echo(f"\nUpdating password for '{service}':") click.echo("Press Enter to keep current values, or type new values:") - - current_username = existing['username'] - current_notes = existing['notes'] - - username = click.prompt("Username/Email", default=current_username) - + + username = click.prompt("Username/Email", default=existing['username']) + if click.confirm("Update password?"): password = getpass.getpass("New password: ") else: - # Keep existing password password = existing['password'] - - notes = click.prompt("Notes", default=current_notes) - + + notes = click.prompt("Notes", default=existing['notes']) + if storage.update_password(service, username, password, notes, master_password): click.echo(f"✓ Password for '{service}' updated successfully!") else: click.echo(f"✗ Failed to update password for '{service}'.") - + except ValueError as e: click.echo(f"✗ Error: {e}") @@ -185,15 +188,16 @@ def update(service: str): @click.option('--force', '-f', is_flag=True, help='Skip confirmation prompt') def delete(service: str, force: bool): """Delete a password entry.""" + service = normalize_service(service) ensure_vault_initialized() - + if not force: if not click.confirm(f"Are you sure you want to delete password for '{service}'?"): click.echo("Cancelled.") return - + master_password = get_master_password() - + try: if storage.delete_password(service, master_password): click.echo(f"✓ Password for '{service}' deleted successfully!") @@ -208,19 +212,19 @@ def delete(service: str, force: bool): def search(keyword: str): """Search for services containing the keyword.""" ensure_vault_initialized() - + master_password = get_master_password() - + try: - results = storage.search_services(keyword, master_password) + results = storage.search_services(keyword.lower(), master_password) if not results: click.echo(f"No services found matching '{keyword}'.") return - + click.echo(f"\nFound {len(results)} service(s) matching '{keyword}':") for entry in results: click.echo(f" • {entry['service']} ({entry['username']})") - + except ValueError as e: click.echo(f"✗ Error: {e}")