Use notify for warnings

This commit is contained in:
thePR0M3TH3AN
2025-07-14 16:36:05 -04:00
parent 8c56bfef66
commit 55df7a3c56
6 changed files with 107 additions and 64 deletions

View File

@@ -723,7 +723,7 @@ class PasswordManager:
Handles the setup process when no existing parent seed is found. Handles the setup process when no existing parent seed is found.
Asks the user whether to enter an existing BIP-85 seed or generate a new one. Asks the user whether to enter an existing BIP-85 seed or generate a new one.
""" """
print(colored("No existing seed found. Let's set up a new one!", "yellow")) self.notify("No existing seed found. Let's set up a new one!", level="WARNING")
choice = input( choice = input(
"Do you want to (1) Enter an existing BIP-85 seed or (2) Generate a new BIP-85 seed? (1/2): " "Do you want to (1) Enter an existing BIP-85 seed or (2) Generate a new BIP-85 seed? (1/2): "
@@ -827,7 +827,7 @@ class PasswordManager:
sys.exit(1) sys.exit(1)
except KeyboardInterrupt: except KeyboardInterrupt:
logging.info("Operation cancelled by user.") logging.info("Operation cancelled by user.")
print(colored("\nOperation cancelled by user.", "yellow")) self.notify("Operation cancelled by user.", level="WARNING")
sys.exit(0) sys.exit(0)
def generate_new_seed(self) -> Optional[str]: def generate_new_seed(self) -> Optional[str]:
@@ -879,7 +879,7 @@ class PasswordManager:
return fingerprint # Return the generated fingerprint return fingerprint # Return the generated fingerprint
else: else:
print(colored("Seed generation cancelled. Exiting.", "yellow")) self.notify("Seed generation cancelled. Exiting.", level="WARNING")
sys.exit(0) sys.exit(0)
def validate_bip85_seed(self, seed: str) -> bool: def validate_bip85_seed(self, seed: str) -> bool:
@@ -1434,7 +1434,7 @@ class PasswordManager:
if not confirm_action( if not confirm_action(
"WARNING: Displaying SSH keys reveals sensitive information. Continue? (Y/N): " "WARNING: Displaying SSH keys reveals sensitive information. Continue? (Y/N): "
): ):
print(colored("SSH key display cancelled.", "yellow")) self.notify("SSH key display cancelled.", level="WARNING")
return return
print(colored(f"\n[+] SSH key entry added with ID {index}.\n", "green")) print(colored(f"\n[+] SSH key entry added with ID {index}.\n", "green"))
@@ -1493,7 +1493,7 @@ class PasswordManager:
if not confirm_action( if not confirm_action(
"WARNING: Displaying the seed phrase reveals sensitive information. Continue? (Y/N): " "WARNING: Displaying the seed phrase reveals sensitive information. Continue? (Y/N): "
): ):
print(colored("Seed phrase display cancelled.", "yellow")) self.notify("Seed phrase display cancelled.", level="WARNING")
return return
print( print(
@@ -1568,7 +1568,7 @@ class PasswordManager:
if not confirm_action( if not confirm_action(
"WARNING: Displaying the PGP key reveals sensitive information. Continue? (Y/N): " "WARNING: Displaying the PGP key reveals sensitive information. Continue? (Y/N): "
): ):
print(colored("PGP key display cancelled.", "yellow")) self.notify("PGP key display cancelled.", level="WARNING")
return return
print(colored(f"\n[+] PGP key entry added with ID {index}.\n", "green")) print(colored(f"\n[+] PGP key entry added with ID {index}.\n", "green"))
@@ -2003,7 +2003,7 @@ class PasswordManager:
entry = self.entry_manager.retrieve_entry(index) or entry entry = self.entry_manager.retrieve_entry(index) or entry
return return
print(colored("No QR codes available for this entry.", "yellow")) self.notify("No QR codes available for this entry.", level="WARNING")
except Exception as e: # pragma: no cover - best effort except Exception as e: # pragma: no cover - best effort
logging.error(f"Error displaying QR menu: {e}", exc_info=True) logging.error(f"Error displaying QR menu: {e}", exc_info=True)
print(colored(f"Error: Failed to display QR codes: {e}", "red")) print(colored(f"Error: Failed to display QR codes: {e}", "red"))
@@ -2103,7 +2103,7 @@ class PasswordManager:
if not confirm_action( if not confirm_action(
"WARNING: Displaying SSH keys reveals sensitive information. Continue? (Y/N): " "WARNING: Displaying SSH keys reveals sensitive information. Continue? (Y/N): "
): ):
print(colored("SSH key display cancelled.", "yellow")) self.notify("SSH key display cancelled.", level="WARNING")
return return
try: try:
priv_pem, pub_pem = self.entry_manager.get_ssh_key_pair( priv_pem, pub_pem = self.entry_manager.get_ssh_key_pair(
@@ -2142,7 +2142,7 @@ class PasswordManager:
if not confirm_action( if not confirm_action(
"WARNING: Displaying the seed phrase reveals sensitive information. Continue? (Y/N): " "WARNING: Displaying the seed phrase reveals sensitive information. Continue? (Y/N): "
): ):
print(colored("Seed phrase display cancelled.", "yellow")) self.notify("Seed phrase display cancelled.", level="WARNING")
return return
try: try:
phrase = self.entry_manager.get_seed_phrase(index, self.parent_seed) phrase = self.entry_manager.get_seed_phrase(index, self.parent_seed)
@@ -2193,7 +2193,7 @@ class PasswordManager:
if not confirm_action( if not confirm_action(
"WARNING: Displaying the PGP key reveals sensitive information. Continue? (Y/N): " "WARNING: Displaying the PGP key reveals sensitive information. Continue? (Y/N): "
): ):
print(colored("PGP key display cancelled.", "yellow")) self.notify("PGP key display cancelled.", level="WARNING")
return return
try: try:
priv_key, fingerprint = self.entry_manager.get_pgp_key( priv_key, fingerprint = self.entry_manager.get_pgp_key(
@@ -2385,11 +2385,9 @@ class PasswordManager:
if url: if url:
print(colored(f"URL: {url}", "cyan")) print(colored(f"URL: {url}", "cyan"))
if blacklisted: if blacklisted:
print( self.notify(
colored( "Warning: This password is archived and should not be used.",
f"Warning: This password is archived and should not be used.", level="WARNING",
"yellow",
)
) )
password = self.password_generator.generate_password(length, index) password = self.password_generator.generate_password(length, index)
@@ -2522,8 +2520,9 @@ class PasswordManager:
if period_input.isdigit(): if period_input.isdigit():
new_period = int(period_input) new_period = int(period_input)
else: else:
print( self.notify(
colored("Invalid period value. Keeping current.", "yellow") "Invalid period value. Keeping current.",
level="WARNING",
) )
digits_input = input( digits_input = input(
f"Enter new digit count (current: {digits}): " f"Enter new digit count (current: {digits}): "
@@ -2533,11 +2532,9 @@ class PasswordManager:
if digits_input.isdigit(): if digits_input.isdigit():
new_digits = int(digits_input) new_digits = int(digits_input)
else: else:
print( self.notify(
colored( "Invalid digits value. Keeping current.",
"Invalid digits value. Keeping current.", level="WARNING",
"yellow",
)
) )
blacklist_input = ( blacklist_input = (
input( input(
@@ -2553,11 +2550,9 @@ class PasswordManager:
elif blacklist_input == "n": elif blacklist_input == "n":
new_blacklisted = False new_blacklisted = False
else: else:
print( self.notify(
colored( "Invalid input for archived status. Keeping the current status.",
"Invalid input for archived status. Keeping the current status.", level="WARNING",
"yellow",
)
) )
new_blacklisted = blacklisted new_blacklisted = blacklisted
@@ -2644,11 +2639,9 @@ class PasswordManager:
elif blacklist_input == "n": elif blacklist_input == "n":
new_blacklisted = False new_blacklisted = False
else: else:
print( self.notify(
colored( "Invalid input for archived status. Keeping the current status.",
"Invalid input for archived status. Keeping the current status.", level="WARNING",
"yellow",
)
) )
new_blacklisted = blacklisted new_blacklisted = blacklisted
@@ -2749,11 +2742,9 @@ class PasswordManager:
elif blacklist_input == "n": elif blacklist_input == "n":
new_blacklisted = False new_blacklisted = False
else: else:
print( self.notify(
colored( "Invalid input for archived status. Keeping the current status.",
"Invalid input for archived status. Keeping the current status.", level="WARNING",
"yellow",
)
) )
new_blacklisted = blacklisted new_blacklisted = blacklisted
@@ -2837,13 +2828,13 @@ class PasswordManager:
) )
query = input("Enter search string: ").strip() query = input("Enter search string: ").strip()
if not query: if not query:
print(colored("No search string provided.", "yellow")) self.notify("No search string provided.", level="WARNING")
pause() pause()
return return
results = self.entry_manager.search_entries(query) results = self.entry_manager.search_entries(query)
if not results: if not results:
print(colored("No matching entries found.", "yellow")) self.notify("No matching entries found.", level="WARNING")
pause() pause()
return return
@@ -3068,7 +3059,7 @@ class PasswordManager:
if not confirm_action( if not confirm_action(
f"Are you sure you want to delete entry {index_to_delete}? (Y/N): " f"Are you sure you want to delete entry {index_to_delete}? (Y/N): "
): ):
print(colored("Deletion cancelled.", "yellow")) self.notify("Deletion cancelled.", level="WARNING")
return return
self.entry_manager.delete_entry(index_to_delete) self.entry_manager.delete_entry(index_to_delete)
@@ -3115,7 +3106,7 @@ class PasswordManager:
archived = self.entry_manager.list_entries(include_archived=True) archived = self.entry_manager.list_entries(include_archived=True)
archived = [e for e in archived if e[4]] archived = [e for e in archived if e[4]]
if not archived: if not archived:
print(colored("No archived entries found.", "yellow")) self.notify("No archived entries found.", level="WARNING")
pause() pause()
return return
while True: while True:
@@ -3196,7 +3187,7 @@ class PasswordManager:
totp_list.append((label, int(idx_str), period, imported)) totp_list.append((label, int(idx_str), period, imported))
if not totp_list: if not totp_list:
print(colored("No 2FA entries found.", "yellow")) self.notify("No 2FA entries found.", level="WARNING")
return return
totp_list.sort(key=lambda t: t[0].lower()) totp_list.sort(key=lambda t: t[0].lower())
@@ -3274,11 +3265,9 @@ class PasswordManager:
try: try:
verified = verify_checksum(current_checksum, SCRIPT_CHECKSUM_FILE) verified = verify_checksum(current_checksum, SCRIPT_CHECKSUM_FILE)
except FileNotFoundError: except FileNotFoundError:
print( self.notify(
colored( "Checksum file missing. Run scripts/update_checksum.py or choose 'Generate Script Checksum' in Settings.",
"Checksum file missing. Run scripts/update_checksum.py or choose 'Generate Script Checksum' in Settings.", level="WARNING",
"yellow",
)
) )
logging.warning("Checksum file missing during verification.") logging.warning("Checksum file missing during verification.")
return return
@@ -3301,7 +3290,7 @@ class PasswordManager:
def handle_update_script_checksum(self) -> None: def handle_update_script_checksum(self) -> None:
"""Generate a new checksum for the manager script.""" """Generate a new checksum for the manager script."""
if not confirm_action("Generate new script checksum? (Y/N): "): if not confirm_action("Generate new script checksum? (Y/N): "):
print(colored("Operation cancelled.", "yellow")) self.notify("Operation cancelled.", level="WARNING")
return return
try: try:
fp, parent_fp, child_fp = self.header_fingerprint_args fp, parent_fp, child_fp = self.header_fingerprint_args
@@ -3500,7 +3489,7 @@ class PasswordManager:
) )
if not totp_entries: if not totp_entries:
print(colored("No 2FA codes to export.", "yellow")) self.notify("No 2FA codes to export.", level="WARNING")
return None return None
dest_str = input( dest_str = input(
@@ -3548,17 +3537,13 @@ class PasswordManager:
child_fingerprint=child_fp, child_fingerprint=child_fp,
) )
print(colored("\n=== Backup Parent Seed ===", "yellow")) print(colored("\n=== Backup Parent Seed ===", "yellow"))
print( self.notify(
colored( "Warning: Revealing your parent seed is a highly sensitive operation.",
"Warning: Revealing your parent seed is a highly sensitive operation.", level="WARNING",
"yellow",
)
) )
print( self.notify(
colored( "Ensure you're in a secure, private environment and no one is watching your screen.",
"Ensure you're in a secure, private environment and no one is watching your screen.", level="WARNING",
"yellow",
)
) )
# Verify user's identity with secure password verification # Verify user's identity with secure password verification
@@ -3573,7 +3558,7 @@ class PasswordManager:
if not confirm_action( if not confirm_action(
"Are you absolutely sure you want to reveal your parent seed? (Y/N): " "Are you absolutely sure you want to reveal your parent seed? (Y/N): "
): ):
print(colored("Operation cancelled by user.", "yellow")) self.notify("Operation cancelled by user.", level="WARNING")
return return
# Reveal the parent seed # Reveal the parent seed

View File

@@ -2,6 +2,7 @@ import sys
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from types import SimpleNamespace from types import SimpleNamespace
import queue
from helpers import create_vault, TEST_SEED, TEST_PASSWORD from helpers import create_vault, TEST_SEED, TEST_PASSWORD
@@ -37,6 +38,7 @@ def test_archive_entry_from_retrieve(monkeypatch):
pm.nostr_client = SimpleNamespace() pm.nostr_client = SimpleNamespace()
pm.fingerprint_dir = tmp_path pm.fingerprint_dir = tmp_path
pm.secret_mode_enabled = False pm.secret_mode_enabled = False
pm.notifications = queue.Queue()
index = entry_mgr.add_entry("example.com", 8) index = entry_mgr.add_entry("example.com", 8)
@@ -68,6 +70,7 @@ def test_restore_entry_from_retrieve(monkeypatch):
pm.nostr_client = SimpleNamespace() pm.nostr_client = SimpleNamespace()
pm.fingerprint_dir = tmp_path pm.fingerprint_dir = tmp_path
pm.secret_mode_enabled = False pm.secret_mode_enabled = False
pm.notifications = queue.Queue()
index = entry_mgr.add_entry("example.com", 8) index = entry_mgr.add_entry("example.com", 8)
entry_mgr.archive_entry(index) entry_mgr.archive_entry(index)

View File

@@ -2,6 +2,7 @@ import sys
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from types import SimpleNamespace from types import SimpleNamespace
import queue
import pytest import pytest
@@ -67,6 +68,7 @@ def test_view_archived_entries_cli(monkeypatch):
pm.nostr_client = SimpleNamespace() pm.nostr_client = SimpleNamespace()
pm.fingerprint_dir = tmp_path pm.fingerprint_dir = tmp_path
pm.is_dirty = False pm.is_dirty = False
pm.notifications = queue.Queue()
idx = entry_mgr.add_entry("example.com", 8) idx = entry_mgr.add_entry("example.com", 8)
@@ -98,6 +100,7 @@ def test_view_archived_entries_view_only(monkeypatch, capsys):
pm.nostr_client = SimpleNamespace() pm.nostr_client = SimpleNamespace()
pm.fingerprint_dir = tmp_path pm.fingerprint_dir = tmp_path
pm.is_dirty = False pm.is_dirty = False
pm.notifications = queue.Queue()
idx = entry_mgr.add_entry("example.com", 8) idx = entry_mgr.add_entry("example.com", 8)
@@ -131,6 +134,7 @@ def test_view_archived_entries_removed_after_restore(monkeypatch, capsys):
pm.nostr_client = SimpleNamespace() pm.nostr_client = SimpleNamespace()
pm.fingerprint_dir = tmp_path pm.fingerprint_dir = tmp_path
pm.is_dirty = False pm.is_dirty = False
pm.notifications = queue.Queue()
idx = entry_mgr.add_entry("example.com", 8) idx = entry_mgr.add_entry("example.com", 8)
@@ -145,5 +149,6 @@ def test_view_archived_entries_removed_after_restore(monkeypatch, capsys):
monkeypatch.setattr("builtins.input", lambda *_: "") monkeypatch.setattr("builtins.input", lambda *_: "")
pm.handle_view_archived_entries() pm.handle_view_archived_entries()
out = capsys.readouterr().out note = pm.notifications.get_nowait()
assert "No archived entries found." in out assert note.level == "WARNING"
assert note.message == "No archived entries found."

View File

@@ -4,6 +4,7 @@ from pathlib import Path
sys.path.append(str(Path(__file__).resolve().parents[1])) sys.path.append(str(Path(__file__).resolve().parents[1]))
from password_manager.manager import PasswordManager, EncryptionMode from password_manager.manager import PasswordManager, EncryptionMode
import queue
class FakeBackupManager: class FakeBackupManager:
@@ -20,6 +21,7 @@ class FakeBackupManager:
def _make_pm(): def _make_pm():
pm = PasswordManager.__new__(PasswordManager) pm = PasswordManager.__new__(PasswordManager)
pm.encryption_mode = EncryptionMode.SEED_ONLY pm.encryption_mode = EncryptionMode.SEED_ONLY
pm.notifications = queue.Queue()
return pm return pm
@@ -56,8 +58,9 @@ def test_handle_verify_checksum_missing(monkeypatch, tmp_path, capsys):
monkeypatch.setattr("password_manager.manager.verify_checksum", raise_missing) monkeypatch.setattr("password_manager.manager.verify_checksum", raise_missing)
pm.handle_verify_checksum() pm.handle_verify_checksum()
out = capsys.readouterr().out.lower() note = pm.notifications.get_nowait()
assert "generate script checksum" in out assert note.level == "WARNING"
assert "generate script checksum" in note.message.lower()
def test_backup_and_restore_database(monkeypatch, capsys): def test_backup_and_restore_database(monkeypatch, capsys):

View File

@@ -0,0 +1,45 @@
import queue
from types import SimpleNamespace
from pathlib import Path
import sys
sys.path.append(str(Path(__file__).resolve().parents[1]))
from password_manager.manager import PasswordManager, EncryptionMode
from password_manager.entry_management import EntryManager
from password_manager.backup import BackupManager
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
from password_manager.config_manager import ConfigManager
def _make_pm(tmp_path: Path) -> PasswordManager:
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.parent_seed = TEST_SEED
pm.nostr_client = SimpleNamespace()
pm.fingerprint_dir = tmp_path
pm.notifications = queue.Queue()
return pm
def test_handle_search_entries_no_query(monkeypatch, tmp_path):
pm = _make_pm(tmp_path)
monkeypatch.setattr(
"password_manager.manager.clear_header_with_notification", lambda *a, **k: None
)
monkeypatch.setattr("password_manager.manager.pause", lambda: None)
monkeypatch.setattr("builtins.input", lambda *_: "")
pm.handle_search_entries()
note = pm.notifications.get_nowait()
assert note.level == "WARNING"
assert note.message == "No search string provided."

View File

@@ -2,6 +2,7 @@ import builtins
import sys import sys
from pathlib import Path from pathlib import Path
from types import SimpleNamespace from types import SimpleNamespace
import queue
sys.path.append(str(Path(__file__).resolve().parents[1])) sys.path.append(str(Path(__file__).resolve().parents[1]))
@@ -16,6 +17,7 @@ def _make_pm(tmp_path: Path) -> PasswordManager:
pm.fingerprint_dir = tmp_path pm.fingerprint_dir = tmp_path
pm.encryption_manager = SimpleNamespace(encrypt_and_save_file=lambda *a, **k: None) pm.encryption_manager = SimpleNamespace(encrypt_and_save_file=lambda *a, **k: None)
pm.verify_password = lambda pw: True pm.verify_password = lambda pw: True
pm.notifications = queue.Queue()
return pm return pm