diff --git a/src/main.py b/src/main.py index 0b56a64..5915c45 100644 --- a/src/main.py +++ b/src/main.py @@ -234,19 +234,40 @@ def handle_display_stats(password_manager: PasswordManager) -> None: print(colored(f"Error: Failed to display stats: {e}", "red")) -def print_matches(matches: list[tuple[int, str, str | None, str | None, bool]]) -> None: +def print_matches( + password_manager: PasswordManager, + matches: list[tuple[int, str, str | None, str | None, bool]], +) -> None: """Print a list of search matches.""" print(colored("\n[+] Matches:\n", "green")) for entry in matches: idx, website, username, url, blacklisted = entry + data = password_manager.entry_manager.retrieve_entry(idx) + etype = ( + data.get("type", data.get("kind", EntryType.PASSWORD.value)) + if data + else EntryType.PASSWORD.value + ) print(colored(f"Index: {idx}", "cyan")) - if website: - print(colored(f" Website: {website}", "cyan")) - if username: - print(colored(f" Username: {username}", "cyan")) - if url: - print(colored(f" URL: {url}", "cyan")) - print(colored(f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan")) + if etype == EntryType.TOTP.value: + print(colored(f" Label: {data.get('label', website)}", "cyan")) + print(colored(f" Derivation Index: {data.get('index', idx)}", "cyan")) + elif etype == EntryType.SEED.value: + print(colored(" Type: Seed Phrase", "cyan")) + elif etype == EntryType.SSH.value: + print(colored(" Type: SSH Key", "cyan")) + elif etype == EntryType.PGP.value: + print(colored(" Type: PGP Key", "cyan")) + elif etype == EntryType.NOSTR.value: + print(colored(" Type: Nostr Key", "cyan")) + else: + if website: + print(colored(f" Website: {website}", "cyan")) + if username: + print(colored(f" Username: {username}", "cyan")) + if url: + print(colored(f" URL: {url}", "cyan")) + print(colored(f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan")) print("-" * 40) @@ -831,7 +852,7 @@ def main(argv: list[str] | None = None) -> int: if args.command == "search": matches = password_manager.entry_manager.search_entries(args.query) if matches: - print_matches(matches) + print_matches(password_manager, matches) else: print(colored("No matching entries found.", "yellow")) return 0 @@ -841,7 +862,7 @@ def main(argv: list[str] | None = None) -> int: if not matches: print(colored("No matching entries found.", "yellow")) else: - print_matches(matches) + print_matches(password_manager, matches) return 1 idx = matches[0][0] entry = password_manager.entry_manager.retrieve_entry(idx) @@ -858,7 +879,7 @@ def main(argv: list[str] | None = None) -> int: if not matches: print(colored("No matching entries found.", "yellow")) else: - print_matches(matches) + print_matches(password_manager, matches) return 1 idx = matches[0][0] entry = password_manager.entry_manager.retrieve_entry(idx) diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 522f366..1647693 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -1694,15 +1694,88 @@ class PasswordManager: return print(colored("\n[+] Search Results:\n", "green")) - for entry in results: - index, website, username, url, blacklisted = entry + for match in results: + index, website, username, url, blacklisted = match + entry = self.entry_manager.retrieve_entry(index) + if not entry: + continue + etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value)) print(colored(f"Index: {index}", "cyan")) - print(colored(f" Website: {website}", "cyan")) - print(colored(f" Username: {username or 'N/A'}", "cyan")) - print(colored(f" URL: {url or 'N/A'}", "cyan")) - print( - colored(f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan") - ) + if etype == EntryType.TOTP.value: + print(colored(f" Label: {entry.get('label', website)}", "cyan")) + print( + colored( + f" Derivation Index: {entry.get('index', index)}", "cyan" + ) + ) + print( + colored( + f" Period: {entry.get('period', 30)}s Digits: {entry.get('digits', 6)}", + "cyan", + ) + ) + notes = entry.get("notes", "") + if notes: + print(colored(f" Notes: {notes}", "cyan")) + elif etype == EntryType.SEED.value: + print(colored(" Type: Seed Phrase", "cyan")) + print(colored(f" Words: {entry.get('words', 24)}", "cyan")) + print( + colored( + f" Derivation Index: {entry.get('index', index)}", "cyan" + ) + ) + notes = entry.get("notes", "") + if notes: + print(colored(f" Notes: {notes}", "cyan")) + elif etype == EntryType.SSH.value: + print(colored(" Type: SSH Key", "cyan")) + print( + colored( + f" Derivation Index: {entry.get('index', index)}", "cyan" + ) + ) + notes = entry.get("notes", "") + if notes: + print(colored(f" Notes: {notes}", "cyan")) + elif etype == EntryType.PGP.value: + print(colored(" Type: PGP Key", "cyan")) + print( + colored( + f" Key Type: {entry.get('key_type', 'ed25519')}", "cyan" + ) + ) + uid = entry.get("user_id", "") + if uid: + print(colored(f" User ID: {uid}", "cyan")) + print( + colored( + f" Derivation Index: {entry.get('index', index)}", "cyan" + ) + ) + notes = entry.get("notes", "") + if notes: + print(colored(f" Notes: {notes}", "cyan")) + elif etype == EntryType.NOSTR.value: + print(colored(" Type: Nostr Key", "cyan")) + print(colored(f" Label: {entry.get('label', '')}", "cyan")) + print( + colored( + f" Derivation Index: {entry.get('index', index)}", "cyan" + ) + ) + notes = entry.get("notes", "") + if notes: + print(colored(f" Notes: {notes}", "cyan")) + else: + print(colored(f" Website: {website}", "cyan")) + print(colored(f" Username: {username or 'N/A'}", "cyan")) + print(colored(f" URL: {url or 'N/A'}", "cyan")) + print( + colored( + f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan" + ) + ) print("-" * 40) except Exception as e: diff --git a/src/tests/test_manager_search_display.py b/src/tests/test_manager_search_display.py new file mode 100644 index 0000000..2c46009 --- /dev/null +++ b/src/tests/test_manager_search_display.py @@ -0,0 +1,41 @@ +import sys +from pathlib import Path +from tempfile import TemporaryDirectory +from types import SimpleNamespace + +from helpers import create_vault, TEST_SEED, TEST_PASSWORD + +sys.path.append(str(Path(__file__).resolve().parents[1])) + +from password_manager.entry_management import EntryManager +from password_manager.backup import BackupManager +from password_manager.manager import PasswordManager, EncryptionMode +from password_manager.config_manager import ConfigManager + + +def test_search_entries_shows_totp_details(monkeypatch, capsys): + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + vault, enc_mgr = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD) + cfg_mgr = ConfigManager(vault, tmp_path) + backup_mgr = BackupManager(tmp_path, cfg_mgr) + entry_mgr = EntryManager(vault, backup_mgr) + + pm = PasswordManager.__new__(PasswordManager) + pm.encryption_mode = EncryptionMode.SEED_ONLY + pm.encryption_manager = enc_mgr + pm.vault = vault + pm.entry_manager = entry_mgr + pm.backup_manager = backup_mgr + pm.nostr_client = SimpleNamespace() + pm.fingerprint_dir = tmp_path + pm.secret_mode_enabled = False + + entry_mgr.add_totp("Example", TEST_SEED) + + monkeypatch.setattr("builtins.input", lambda *a, **k: "Example") + + pm.handle_search_entries() + out = capsys.readouterr().out + assert "Label: Example" in out + assert "Derivation Index" in out