diff --git a/README.md b/README.md index 1f0a6c9..011ab13 100644 --- a/README.md +++ b/README.md @@ -267,9 +267,10 @@ When choosing **Add Entry**, you can now select from: 1. From the main menu choose **Modify an Existing Entry** and enter the index of the 2FA code you want to edit. 2. SeedPass will show the current label, period, digit count, and archived status. 3. Enter new values or press **Enter** to keep the existing settings. -4. The updated entry is saved back to your encrypted vault. -5. Archived entries are hidden from lists but can be viewed or restored from the **List Archived** menu. -6. When editing an archived entry you'll be prompted to restore it after saving your changes. +4. When retrieving a 2FA entry you can press **E** to edit the label, period or digit count, or **A** to archive/unarchive it. +5. The updated entry is saved back to your encrypted vault. +6. Archived entries are hidden from lists but can be viewed or restored from the **List Archived** menu. +7. When editing an archived entry you'll be prompted to restore it after saving your changes. ### Using Secret Mode diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index df46bd1..d595ff1 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -1674,6 +1674,9 @@ class PasswordManager: if entry_type == EntryType.PASSWORD.value: print(colored("U. Edit Username", "cyan")) print(colored("R. Edit URL", "cyan")) + elif entry_type == EntryType.TOTP.value: + print(colored("P. Edit Period", "cyan")) + print(colored("D. Edit Digits", "cyan")) choice = input("Select option or press Enter to go back: ").strip().lower() if not choice: break @@ -1693,6 +1696,22 @@ class PasswordManager: self.entry_manager.modify_entry(index, url=new_url) self.is_dirty = True self.last_update = time.time() + elif entry_type == EntryType.TOTP.value and choice == "p": + period_str = input("New period (seconds): ").strip() + if period_str.isdigit(): + self.entry_manager.modify_entry(index, period=int(period_str)) + self.is_dirty = True + self.last_update = time.time() + else: + print(colored("Invalid period value.", "red")) + elif entry_type == EntryType.TOTP.value and choice == "d": + digits_str = input("New digits: ").strip() + if digits_str.isdigit(): + self.entry_manager.modify_entry(index, digits=int(digits_str)) + self.is_dirty = True + self.last_update = time.time() + else: + print(colored("Invalid digits value.", "red")) else: print(colored("Invalid choice.", "red")) entry = self.entry_manager.retrieve_entry(index) or entry diff --git a/src/tests/test_manager_edit_totp.py b/src/tests/test_manager_edit_totp.py new file mode 100644 index 0000000..53e43d4 --- /dev/null +++ b/src/tests/test_manager_edit_totp.py @@ -0,0 +1,57 @@ +import sys +from pathlib import Path +from tempfile import TemporaryDirectory + +from helpers import create_vault, TEST_SEED, TEST_PASSWORD + +sys.path.append(str(Path(__file__).resolve().parents[1])) + +from password_manager.entry_management import EntryManager +from password_manager.backup import BackupManager +from password_manager.manager import PasswordManager, EncryptionMode +from password_manager.config_manager import ConfigManager + + +class FakeNostrClient: + def __init__(self, *args, **kwargs): + self.published = [] + + def publish_snapshot(self, data: bytes): + self.published.append(data) + return None, "abcd" + + +def test_edit_totp_period_from_retrieve(monkeypatch): + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + 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 = FakeNostrClient() + pm.fingerprint_dir = tmp_path + pm.is_dirty = False + pm.secret_mode_enabled = False + + entry_mgr.add_totp("Example", TEST_SEED) + + inputs = iter(["0", "e", "p", "45", "", ""]) + monkeypatch.setattr("builtins.input", lambda *a, **k: next(inputs)) + monkeypatch.setattr(pm.entry_manager, "get_totp_code", lambda *a, **k: "123456") + monkeypatch.setattr( + pm.entry_manager, "get_totp_time_remaining", lambda *a, **k: 1 + ) + monkeypatch.setattr("password_manager.manager.time.sleep", lambda *a, **k: None) + monkeypatch.setattr("password_manager.manager.timed_input", lambda *a, **k: "b") + + pm.handle_retrieve_entry() + entry = entry_mgr.retrieve_entry(0) + assert entry["period"] == 45 diff --git a/src/tests/test_modify_totp_entry.py b/src/tests/test_modify_totp_entry.py new file mode 100644 index 0000000..b1cb825 --- /dev/null +++ b/src/tests/test_modify_totp_entry.py @@ -0,0 +1,20 @@ +from helpers import create_vault, TEST_SEED, TEST_PASSWORD + +from password_manager.entry_management import EntryManager +from password_manager.backup import BackupManager +from password_manager.config_manager import ConfigManager + + +def test_modify_totp_entry_period_digits_and_archive(tmp_path): + vault, _ = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD) + cfg_mgr = ConfigManager(vault, tmp_path) + backup_mgr = BackupManager(tmp_path, cfg_mgr) + em = EntryManager(vault, backup_mgr) + + em.add_totp("Example", TEST_SEED, period=30, digits=6) + em.modify_entry(0, period=60, digits=8, archived=True) + + entry = em.retrieve_entry(0) + assert entry["period"] == 60 + assert entry["digits"] == 8 + assert entry["archived"] is True