From ce90e7348f267f19d8d9b97c39469355a5655ca6 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sat, 5 Jul 2025 09:24:26 -0400 Subject: [PATCH] Add list entries feature --- src/main.py | 22 ++- src/password_manager/entry_management.py | 36 ++++ src/password_manager/manager.py | 199 +++++++++++++---------- 3 files changed, 165 insertions(+), 92 deletions(-) diff --git a/src/main.py b/src/main.py index 5915c45..f17c94c 100644 --- a/src/main.py +++ b/src/main.py @@ -701,10 +701,11 @@ def display_menu( 1. Add Entry 2. Retrieve Entry 3. Search Entries - 4. Modify an Existing Entry - 5. 2FA Codes - 6. Settings - 7. Exit + 4. List Entries + 5. Modify an Existing Entry + 6. 2FA Codes + 7. Settings + 8. Exit """ display_fn = getattr(password_manager, "display_stats", None) if callable(display_fn): @@ -729,7 +730,7 @@ def display_menu( print(colored(menu, "cyan")) try: choice = timed_input( - "Enter your choice (1-7): ", inactivity_timeout + "Enter your choice (1-8): ", inactivity_timeout ).strip() except TimeoutError: print(colored("Session timed out. Vault locked.", "yellow")) @@ -740,7 +741,7 @@ def display_menu( if not choice: print( colored( - "No input detected. Please enter a number between 1 and 7.", + "No input detected. Please enter a number between 1 and 8.", "yellow", ) ) @@ -787,14 +788,17 @@ def display_menu( password_manager.handle_search_entries() elif choice == "4": password_manager.update_activity() - password_manager.handle_modify_entry() + password_manager.handle_list_entries() elif choice == "5": password_manager.update_activity() - password_manager.handle_display_totp_codes() + password_manager.handle_modify_entry() elif choice == "6": password_manager.update_activity() - handle_settings(password_manager) + password_manager.handle_display_totp_codes() elif choice == "7": + password_manager.update_activity() + handle_settings(password_manager) + elif choice == "8": logging.info("Exiting the program.") print(colored("Exiting the program.", "green")) password_manager.nostr_client.close_client_pool() diff --git a/src/password_manager/entry_management.py b/src/password_manager/entry_management.py index 48a6081..0f16424 100644 --- a/src/password_manager/entry_management.py +++ b/src/password_manager/entry_management.py @@ -841,3 +841,39 @@ class EntryManager: logger.error(f"Failed to list all entries: {e}", exc_info=True) print(colored(f"Error: Failed to list all entries: {e}", "red")) return + + def get_entry_summaries( + self, filter_kind: str | None = None + ) -> list[tuple[int, str]]: + """Return a list of entry index and display labels.""" + try: + data = self.vault.load_index() + entries_data = data.get("entries", {}) + + summaries: list[tuple[int, str]] = [] + for idx_str, entry in entries_data.items(): + etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value)) + if filter_kind and etype != filter_kind: + continue + if etype == EntryType.PASSWORD.value: + label = entry.get("website", "") + elif etype == EntryType.TOTP.value: + label = entry.get("label", "") + elif etype == EntryType.SSH.value: + label = "SSH Key" + elif etype == EntryType.SEED.value: + label = "Seed Phrase" + elif etype == EntryType.NOSTR.value: + label = entry.get("label", "Nostr Key") + elif etype == EntryType.PGP.value: + label = "PGP Key" + else: + label = etype + summaries.append((int(idx_str), label)) + + summaries.sort(key=lambda x: x[0]) + return summaries + except Exception as e: + logger.error(f"Failed to get entry summaries: {e}", exc_info=True) + print(colored(f"Error: Failed to get entry summaries: {e}", "red")) + return [] diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 6e16c7f..4b20908 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -1752,93 +1752,126 @@ class PasswordManager: print(colored("\n[+] Search Results:\n", "green")) 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")) - 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) - + self.display_entry_details(match[0]) except Exception as e: logging.error(f"Failed to search entries: {e}", exc_info=True) print(colored(f"Error: Failed to search entries: {e}", "red")) + def display_entry_details(self, index: int) -> None: + """Print detailed information for a single entry.""" + entry = self.entry_manager.retrieve_entry(index) + if not entry: + return + + etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value)) + print(colored(f"Index: {index}", "cyan")) + if etype == EntryType.TOTP.value: + print(colored(f" Label: {entry.get('label', '')}", "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: + website = entry.get("website", "") + username = entry.get("username", "") + url = entry.get("url", "") + blacklisted = entry.get("blacklisted", False) + 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) + + def handle_list_entries(self) -> None: + """List entries and optionally show details.""" + try: + while True: + print("\nList Entries:") + print("1. All") + print("2. Passwords") + print("3. 2FA (TOTP)") + print("4. SSH Key") + print("5. Seed Phrase") + print("6. Nostr Key Pair") + print("7. PGP") + print("8. Back") + choice = input("Select entry type: ").strip() + if choice == "1": + filter_kind = None + elif choice == "2": + filter_kind = EntryType.PASSWORD.value + elif choice == "3": + filter_kind = EntryType.TOTP.value + elif choice == "4": + filter_kind = EntryType.SSH.value + elif choice == "5": + filter_kind = EntryType.SEED.value + elif choice == "6": + filter_kind = EntryType.NOSTR.value + elif choice == "7": + filter_kind = EntryType.PGP.value + elif choice == "8": + return + else: + print(colored("Invalid choice.", "red")) + continue + + summaries = self.entry_manager.get_entry_summaries(filter_kind) + if not summaries: + continue + print(colored("\n[+] Entries:\n", "green")) + for idx, label in summaries: + print(colored(f"{idx}. {label}", "cyan")) + idx_input = input( + "Enter index to view details or press Enter to return: " + ).strip() + if not idx_input: + return + if not idx_input.isdigit(): + print(colored("Invalid index.", "red")) + continue + self.display_entry_details(int(idx_input)) + return + except Exception as e: + logging.error(f"Failed to list entries: {e}", exc_info=True) + print(colored(f"Error: Failed to list entries: {e}", "red")) + def delete_entry(self) -> None: """Deletes an entry from the password index.""" try: