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

View File

@@ -2,6 +2,7 @@ import sys
from pathlib import Path
from tempfile import TemporaryDirectory
from types import SimpleNamespace
import queue
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.fingerprint_dir = tmp_path
pm.secret_mode_enabled = False
pm.notifications = queue.Queue()
index = entry_mgr.add_entry("example.com", 8)
@@ -68,6 +70,7 @@ def test_restore_entry_from_retrieve(monkeypatch):
pm.nostr_client = SimpleNamespace()
pm.fingerprint_dir = tmp_path
pm.secret_mode_enabled = False
pm.notifications = queue.Queue()
index = entry_mgr.add_entry("example.com", 8)
entry_mgr.archive_entry(index)

View File

@@ -2,6 +2,7 @@ import sys
from pathlib import Path
from tempfile import TemporaryDirectory
from types import SimpleNamespace
import queue
import pytest
@@ -67,6 +68,7 @@ def test_view_archived_entries_cli(monkeypatch):
pm.nostr_client = SimpleNamespace()
pm.fingerprint_dir = tmp_path
pm.is_dirty = False
pm.notifications = queue.Queue()
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.fingerprint_dir = tmp_path
pm.is_dirty = False
pm.notifications = queue.Queue()
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.fingerprint_dir = tmp_path
pm.is_dirty = False
pm.notifications = queue.Queue()
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 *_: "")
pm.handle_view_archived_entries()
out = capsys.readouterr().out
assert "No archived entries found." in out
note = pm.notifications.get_nowait()
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]))
from password_manager.manager import PasswordManager, EncryptionMode
import queue
class FakeBackupManager:
@@ -20,6 +21,7 @@ class FakeBackupManager:
def _make_pm():
pm = PasswordManager.__new__(PasswordManager)
pm.encryption_mode = EncryptionMode.SEED_ONLY
pm.notifications = queue.Queue()
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)
pm.handle_verify_checksum()
out = capsys.readouterr().out.lower()
assert "generate script checksum" in out
note = pm.notifications.get_nowait()
assert note.level == "WARNING"
assert "generate script checksum" in note.message.lower()
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
from pathlib import Path
from types import SimpleNamespace
import queue
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.encryption_manager = SimpleNamespace(encrypt_and_save_file=lambda *a, **k: None)
pm.verify_password = lambda pw: True
pm.notifications = queue.Queue()
return pm