mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 23:38:49 +00:00
198 lines
8.9 KiB
Python
198 lines
8.9 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import sys
|
|
from typing import TYPE_CHECKING
|
|
|
|
from termcolor import colored
|
|
|
|
from .entry_types import EntryType
|
|
import seedpass.core.manager as manager_module
|
|
from utils.color_scheme import color_text
|
|
from utils.input_utils import timed_input
|
|
from utils.terminal_utils import clear_header_with_notification
|
|
|
|
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
from .manager import PasswordManager
|
|
|
|
|
|
class MenuHandler:
|
|
"""Handle interactive menu operations for :class:`PasswordManager`."""
|
|
|
|
def __init__(self, manager: PasswordManager) -> None:
|
|
self.manager = manager
|
|
|
|
def handle_list_entries(self) -> None:
|
|
"""List entries and optionally show details."""
|
|
pm = self.manager
|
|
try:
|
|
while True:
|
|
fp, parent_fp, child_fp = pm.header_fingerprint_args
|
|
clear_header_with_notification(
|
|
pm,
|
|
fp,
|
|
"Main Menu > List Entries",
|
|
parent_fingerprint=parent_fp,
|
|
child_fingerprint=child_fp,
|
|
)
|
|
print(color_text("\nList Entries:", "menu"))
|
|
print(color_text("1. All", "menu"))
|
|
print(color_text("2. Passwords", "menu"))
|
|
print(color_text("3. 2FA (TOTP)", "menu"))
|
|
print(color_text("4. SSH Key", "menu"))
|
|
print(color_text("5. Seed Phrase", "menu"))
|
|
print(color_text("6. Nostr Key Pair", "menu"))
|
|
print(color_text("7. PGP", "menu"))
|
|
print(color_text("8. Key/Value", "menu"))
|
|
print(color_text("9. Managed Account", "menu"))
|
|
choice = input("Select entry type or press Enter to go back: ").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":
|
|
filter_kind = EntryType.KEY_VALUE.value
|
|
elif choice == "9":
|
|
filter_kind = EntryType.MANAGED_ACCOUNT.value
|
|
elif not choice:
|
|
return
|
|
else:
|
|
print(colored("Invalid choice.", "red"))
|
|
continue
|
|
|
|
while True:
|
|
summaries = pm.entry_manager.get_entry_summaries(
|
|
filter_kind, include_archived=False
|
|
)
|
|
if not summaries:
|
|
break
|
|
fp, parent_fp, child_fp = pm.header_fingerprint_args
|
|
clear_header_with_notification(
|
|
pm,
|
|
fp,
|
|
"Main Menu > List Entries",
|
|
parent_fingerprint=parent_fp,
|
|
child_fingerprint=child_fp,
|
|
)
|
|
print(colored("\n[+] Entries:\n", "green"))
|
|
for idx, etype, label in summaries:
|
|
if filter_kind is None:
|
|
display_type = etype.capitalize()
|
|
print(colored(f"{idx}. {display_type} - {label}", "cyan"))
|
|
else:
|
|
print(colored(f"{idx}. {label}", "cyan"))
|
|
idx_input = input(
|
|
"Enter index to view details or press Enter to go back: "
|
|
).strip()
|
|
if not idx_input:
|
|
break
|
|
if not idx_input.isdigit():
|
|
print(colored("Invalid index.", "red"))
|
|
continue
|
|
pm.show_entry_details_by_index(int(idx_input))
|
|
except Exception as e: # pragma: no cover - defensive
|
|
logging.error(f"Failed to list entries: {e}", exc_info=True)
|
|
print(colored(f"Error: Failed to list entries: {e}", "red"))
|
|
|
|
def handle_display_totp_codes(self) -> None:
|
|
"""Display all stored TOTP codes with a countdown progress bar."""
|
|
pm = self.manager
|
|
try:
|
|
fp, parent_fp, child_fp = pm.header_fingerprint_args
|
|
clear_header_with_notification(
|
|
pm,
|
|
fp,
|
|
"Main Menu > 2FA Codes",
|
|
parent_fingerprint=parent_fp,
|
|
child_fingerprint=child_fp,
|
|
)
|
|
data = pm.entry_manager.vault.load_index()
|
|
entries = data.get("entries", {})
|
|
totp_list: list[tuple[str, int, int, bool]] = []
|
|
for idx_str, entry in entries.items():
|
|
if pm._entry_type_str(entry) == EntryType.TOTP.value and not entry.get(
|
|
"archived", entry.get("blacklisted", False)
|
|
):
|
|
label = entry.get("label", "")
|
|
period = int(entry.get("period", 30))
|
|
imported = "secret" in entry
|
|
totp_list.append((label, int(idx_str), period, imported))
|
|
|
|
if not totp_list:
|
|
pm.notify("No 2FA entries found.", level="WARNING")
|
|
return
|
|
|
|
totp_list.sort(key=lambda t: t[0].lower())
|
|
print(colored("Press Enter to return to the menu.", "cyan"))
|
|
while True:
|
|
fp, parent_fp, child_fp = pm.header_fingerprint_args
|
|
clear_header_with_notification(
|
|
pm,
|
|
fp,
|
|
"Main Menu > 2FA Codes",
|
|
parent_fingerprint=parent_fp,
|
|
child_fingerprint=child_fp,
|
|
)
|
|
print(colored("Press Enter to return to the menu.", "cyan"))
|
|
generated = [t for t in totp_list if not t[3]]
|
|
imported_list = [t for t in totp_list if t[3]]
|
|
if generated:
|
|
print(colored("\nGenerated 2FA Codes:", "green"))
|
|
for label, idx, period, _ in generated:
|
|
code = pm.entry_manager.get_totp_code(idx, pm.parent_seed)
|
|
remaining = pm.entry_manager.get_totp_time_remaining(idx)
|
|
filled = int(20 * (period - remaining) / period)
|
|
bar = "[" + "#" * filled + "-" * (20 - filled) + "]"
|
|
if pm.secret_mode_enabled:
|
|
if manager_module.copy_to_clipboard(
|
|
code, pm.clipboard_clear_delay
|
|
):
|
|
print(
|
|
f"[{idx}] {label}: [HIDDEN] {bar} {remaining:2d}s - copied to clipboard"
|
|
)
|
|
else:
|
|
print(
|
|
f"[{idx}] {label}: {color_text(code, 'deterministic')} {bar} {remaining:2d}s"
|
|
)
|
|
if imported_list:
|
|
print(colored("\nImported 2FA Codes:", "green"))
|
|
for label, idx, period, _ in imported_list:
|
|
code = pm.entry_manager.get_totp_code(idx, pm.parent_seed)
|
|
remaining = pm.entry_manager.get_totp_time_remaining(idx)
|
|
filled = int(20 * (period - remaining) / period)
|
|
bar = "[" + "#" * filled + "-" * (20 - filled) + "]"
|
|
if pm.secret_mode_enabled:
|
|
if manager_module.copy_to_clipboard(
|
|
code, pm.clipboard_clear_delay
|
|
):
|
|
print(
|
|
f"[{idx}] {label}: [HIDDEN] {bar} {remaining:2d}s - copied to clipboard"
|
|
)
|
|
else:
|
|
print(
|
|
f"[{idx}] {label}: {color_text(code, 'imported')} {bar} {remaining:2d}s"
|
|
)
|
|
sys.stdout.flush()
|
|
try:
|
|
user_input = timed_input("", 1)
|
|
if user_input.strip() == "" or user_input.strip().lower() == "b":
|
|
break
|
|
except TimeoutError:
|
|
pass
|
|
except KeyboardInterrupt:
|
|
print()
|
|
break
|
|
except Exception as e: # pragma: no cover - defensive
|
|
logging.error(f"Error displaying TOTP codes: {e}", exc_info=True)
|
|
print(colored(f"Error: Failed to display TOTP codes: {e}", "red"))
|