mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-06 04:42:35 +00:00
Add service classes and tests
This commit is contained in:
1 parent
fddc169433
commit
08c4453326
5 files changed
+708
-470
No files matched your search
@@ -0,0 +1,197 @@
|
||||
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"))
|
||||
Reference in new issue
Block a user