From bacf32b46ed5d173fd0204bc8b529a2d64478fdd Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sat, 5 Jul 2025 10:00:46 -0400 Subject: [PATCH] Remove deprecated website field --- README.md | 4 +- docs/advanced_cli.md | 6 +- docs/json_entries.md | 2 +- src/main.py | 2 +- src/password_manager/entry_management.py | 94 ++++++++++++++---------- src/password_manager/manager.py | 33 +++++++-- src/password_manager/migrations.py | 8 ++ src/tests/test_backup_restore.py | 4 +- src/tests/test_entry_add.py | 6 +- src/tests/test_index_import_export.py | 4 +- src/tests/test_migrations.py | 6 +- src/tests/test_nostr_index_size.py | 2 +- src/tests/test_pgp_entry.py | 4 +- src/tests/test_seed_entry.py | 4 +- src/tests/test_ssh_entry.py | 10 ++- src/tests/test_ssh_entry_valid.py | 2 +- 16 files changed, 123 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 4026271..3521d23 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ seedpass totp "email" # The code is printed and copied to your clipboard # Sort or filter the list view -seedpass list --sort website +seedpass list --sort label seedpass list --filter totp # Use the **Settings** menu to configure an extra backup directory @@ -185,7 +185,7 @@ The encrypted index file `seedpass_entries_db.json.enc` begins with `schema_vers "schema_version": 2, "entries": { "0": { - "website": "example.com", + "label": "example.com", "length": 8, "type": "password", "notes": "" diff --git a/docs/advanced_cli.md b/docs/advanced_cli.md index 4288b9b..2f5aeda 100644 --- a/docs/advanced_cli.md +++ b/docs/advanced_cli.md @@ -53,7 +53,7 @@ The following table provides a quick reference to all available advanced CLI com | Retrieve a password entry | `retrieve` | `-R` | `--retrieve` | `seedpass retrieve --index 3` or `seedpass retrieve --title "GitHub"` | | Modify an existing entry | `modify` | `-M` | `--modify` | `seedpass modify --index 3 --title "GitHub Pro" --notes "Updated to pro account" --tags "work,development,pro" --length 22` | | Delete an entry | `delete` | `-D` | `--delete` | `seedpass delete --index 3` | -| List all entries | `list` | `-L` | `--list` | `seedpass list --sort website` | +| List all entries | `list` | `-L` | `--list` | `seedpass list --sort label` | | Search for a password entry | `search` | `-S` | `--search` | `seedpass search "GitHub"` | | Get password from query | `get` | | | `seedpass get "GitHub"` | Display a TOTP code | `totp` | | | `seedpass totp "email"` @@ -179,11 +179,11 @@ seedpass delete --index 3 **Long Flag:** `--list` **Description:** -Lists all password entries stored in the password manager. You can sort the output by index, website, or username and filter by entry type. +Lists all password entries stored in the password manager. You can sort the output by index, label, or username and filter by entry type. **Usage Example:** ```bash -seedpass list --sort website +seedpass list --sort label seedpass list --filter totp ``` diff --git a/docs/json_entries.md b/docs/json_entries.md index 9d09834..1fd1e0b 100644 --- a/docs/json_entries.md +++ b/docs/json_entries.md @@ -42,7 +42,7 @@ All entries belonging to a seed profile are summarized in an encrypted file name "schema_version": 2, "entries": { "0": { - "website": "example.com", + "label": "example.com", "length": 8, "type": "password", "notes": "" diff --git a/src/main.py b/src/main.py index f17c94c..b7cc7fd 100644 --- a/src/main.py +++ b/src/main.py @@ -262,7 +262,7 @@ def print_matches( print(colored(" Type: Nostr Key", "cyan")) else: if website: - print(colored(f" Website: {website}", "cyan")) + print(colored(f" Label: {website}", "cyan")) if username: print(colored(f" Username: {username}", "cyan")) if url: diff --git a/src/password_manager/entry_management.py b/src/password_manager/entry_management.py index 0f16424..125fcec 100644 --- a/src/password_manager/entry_management.py +++ b/src/password_manager/entry_management.py @@ -65,6 +65,13 @@ class EntryManager: if "kind" not in entry: entry["kind"] = entry.get("type", EntryType.PASSWORD.value) entry.setdefault("type", entry["kind"]) + if "label" not in entry and "website" in entry: + entry["label"] = entry["website"] + if ( + "website" in entry + and entry.get("type") == EntryType.PASSWORD.value + ): + entry.pop("website", None) logger.debug("Index loaded successfully.") return data except Exception as e: @@ -106,7 +113,7 @@ class EntryManager: def add_entry( self, - website_name: str, + label: str, length: int, username: Optional[str] = None, url: Optional[str] = None, @@ -117,7 +124,7 @@ class EntryManager: """ Adds a new entry to the encrypted JSON index file. - :param website_name: The name of the website. + :param label: A label describing the entry (e.g. website name). :param length: The desired length of the password. :param username: (Optional) The username associated with the website. :param url: (Optional) The URL of the website. @@ -131,7 +138,7 @@ class EntryManager: data.setdefault("entries", {}) data["entries"][str(index)] = { - "website": website_name, + "label": label, "length": length, "username": username if username else "", "url": url if url else "", @@ -222,7 +229,11 @@ class EntryManager: raise def add_ssh_key( - self, parent_seed: str, index: int | None = None, notes: str = "" + self, + label: str, + parent_seed: str, + index: int | None = None, + notes: str = "", ) -> int: """Add a new SSH key pair entry. @@ -240,6 +251,7 @@ class EntryManager: "type": EntryType.SSH.value, "kind": EntryType.SSH.value, "index": index, + "label": label, "notes": notes, } self._save_index(data) @@ -263,6 +275,7 @@ class EntryManager: def add_pgp_key( self, + label: str, parent_seed: str, index: int | None = None, key_type: str = "ed25519", @@ -280,6 +293,7 @@ class EntryManager: "type": EntryType.PGP.value, "kind": EntryType.PGP.value, "index": index, + "label": label, "key_type": key_type, "user_id": user_id, "notes": notes, @@ -362,6 +376,7 @@ class EntryManager: def add_seed( self, + label: str, parent_seed: str, index: int | None = None, words_num: int = 24, @@ -378,6 +393,7 @@ class EntryManager: "type": EntryType.SEED.value, "kind": EntryType.SEED.value, "index": index, + "label": label, "words": words_num, "notes": notes, } @@ -596,11 +612,11 @@ class EntryManager: idx_str, entry = item if sort_by == "index": return int(idx_str) - if sort_by == "website": - return entry.get("website", "").lower() + if sort_by in {"website", "label"}: + return entry.get("label", entry.get("website", "")).lower() if sort_by == "username": return entry.get("username", "").lower() - raise ValueError("sort_by must be 'index', 'website', or 'username'") + raise ValueError("sort_by must be 'index', 'label', or 'username'") sorted_items = sorted(entries_data.items(), key=sort_key) @@ -616,19 +632,20 @@ class EntryManager: entries: List[Tuple[int, str, Optional[str], Optional[str], bool]] = [] for idx, entry in filtered_items: + label = entry.get("label", entry.get("website", "")) etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value)) - if etype == EntryType.TOTP.value: - entries.append((idx, entry.get("label", ""), None, None, False)) - else: + if etype == EntryType.PASSWORD.value: entries.append( ( idx, - entry.get("website", ""), + label, entry.get("username", ""), entry.get("url", ""), entry.get("blacklisted", False), ) ) + else: + entries.append((idx, label, None, None, False)) logger.debug(f"Total entries found: {len(entries)}") for idx, entry in filtered_items: @@ -644,8 +661,13 @@ class EntryManager: "cyan", ) ) - else: - print(colored(f" Website: {entry.get('website', '')}", "cyan")) + elif etype == EntryType.PASSWORD.value: + print( + colored( + f" Label: {entry.get('label', entry.get('website', ''))}", + "cyan", + ) + ) print( colored(f" Username: {entry.get('username') or 'N/A'}", "cyan") ) @@ -656,6 +678,13 @@ class EntryManager: "cyan", ) ) + else: + print(colored(f" Label: {entry.get('label', '')}", "cyan")) + print( + colored( + f" Derivation Index: {entry.get('index', index)}", "cyan" + ) + ) print("-" * 40) return entries @@ -680,16 +709,14 @@ class EntryManager: for idx, entry in sorted(entries_data.items(), key=lambda x: int(x[0])): etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value)) - if etype == EntryType.TOTP.value: - label = entry.get("label", "") - notes = entry.get("notes", "") - if query_lower in label.lower() or query_lower in notes.lower(): - results.append((int(idx), label, None, None, False)) - else: - website = entry.get("website", "") + label = entry.get("label", entry.get("website", "")) + notes = entry.get("notes", "") + label_match = query_lower in label.lower() + notes_match = query_lower in notes.lower() + + if etype == EntryType.PASSWORD.value: username = entry.get("username", "") url = entry.get("url", "") - notes = entry.get("notes", "") custom_fields = entry.get("custom_fields", []) custom_match = any( query_lower in str(cf.get("label", "")).lower() @@ -697,21 +724,24 @@ class EntryManager: for cf in custom_fields ) if ( - query_lower in website.lower() + label_match or query_lower in username.lower() or query_lower in url.lower() - or query_lower in notes.lower() + or notes_match or custom_match ): results.append( ( int(idx), - website, + label, username, url, entry.get("blacklisted", False), ) ) + else: + if label_match or notes_match: + results.append((int(idx), label, None, None, False)) return results @@ -829,7 +859,7 @@ class EntryManager: for entry in entries: index, website, username, url, blacklisted = entry print(colored(f"Index: {index}", "cyan")) - print(colored(f" Website: {website}", "cyan")) + print(colored(f" Label: {website}", "cyan")) print(colored(f" Username: {username or 'N/A'}", "cyan")) print(colored(f" URL: {url or 'N/A'}", "cyan")) print( @@ -856,19 +886,9 @@ class EntryManager: if filter_kind and etype != filter_kind: continue if etype == EntryType.PASSWORD.value: - label = entry.get("website", "") - elif etype == EntryType.TOTP.value: - label = entry.get("label", "") - elif etype == EntryType.SSH.value: - label = "SSH Key" - elif etype == EntryType.SEED.value: - label = "Seed Phrase" - elif etype == EntryType.NOSTR.value: - label = entry.get("label", "Nostr Key") - elif etype == EntryType.PGP.value: - label = "PGP Key" + label = entry.get("label", entry.get("website", "")) else: - label = etype + label = entry.get("label", etype) summaries.append((int(idx_str), label)) summaries.sort(key=lambda x: x[0]) diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 21e62f2..427f80f 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -859,9 +859,9 @@ class PasswordManager: def handle_add_password(self) -> None: try: - website_name = input("Enter the website name: ").strip() + website_name = input("Enter the label or website name: ").strip() if not website_name: - print(colored("Error: Website name cannot be empty.", "red")) + print(colored("Error: Label cannot be empty.", "red")) return username = input("Enter the username (optional): ").strip() @@ -1040,8 +1040,12 @@ class PasswordManager: def handle_add_ssh_key(self) -> None: """Add an SSH key pair entry and display the derived keys.""" try: + label = input("Label: ").strip() + if not label: + print(colored("Error: Label cannot be empty.", "red")) + return notes = input("Notes (optional): ").strip() - index = self.entry_manager.add_ssh_key(self.parent_seed, notes=notes) + index = self.entry_manager.add_ssh_key(label, self.parent_seed, notes=notes) priv_pem, pub_pem = self.entry_manager.get_ssh_key_pair( index, self.parent_seed ) @@ -1075,6 +1079,10 @@ class PasswordManager: def handle_add_seed(self) -> None: """Add a derived BIP-39 seed phrase entry.""" try: + label = input("Label: ").strip() + if not label: + print(colored("Error: Label cannot be empty.", "red")) + return words_input = input("Word count (12 or 24, default 24): ").strip() notes = input("Notes (optional): ").strip() if words_input and words_input not in {"12", "24"}: @@ -1082,7 +1090,7 @@ class PasswordManager: return words = int(words_input) if words_input else 24 index = self.entry_manager.add_seed( - self.parent_seed, words_num=words, notes=notes + label, self.parent_seed, words_num=words, notes=notes ) phrase = self.entry_manager.get_seed_phrase(index, self.parent_seed) self.is_dirty = True @@ -1117,6 +1125,10 @@ class PasswordManager: def handle_add_pgp(self) -> None: """Add a PGP key entry and display the generated key.""" try: + label = input("Label: ").strip() + if not label: + print(colored("Error: Label cannot be empty.", "red")) + return key_type = ( input("Key type (ed25519 or rsa, default ed25519): ").strip().lower() or "ed25519" @@ -1124,6 +1136,7 @@ class PasswordManager: user_id = input("User ID (optional): ").strip() notes = input("Notes (optional): ").strip() index = self.entry_manager.add_pgp_key( + label, self.parent_seed, key_type=key_type, user_id=user_id, @@ -1162,7 +1175,10 @@ class PasswordManager: def handle_add_nostr_key(self) -> None: """Add a Nostr key entry and display the derived keys.""" try: - label = input("Label (optional): ").strip() + label = input("Label: ").strip() + if not label: + print(colored("Error: Label cannot be empty.", "red")) + return notes = input("Notes (optional): ").strip() index = self.entry_manager.add_nostr_key(label, notes=notes) npub, nsec = self.entry_manager.get_nostr_key_pair(index, self.parent_seed) @@ -1779,6 +1795,7 @@ class PasswordManager: print(colored(f" Notes: {notes}", "cyan")) elif etype == EntryType.SEED.value: print(colored(" Type: Seed Phrase", "cyan")) + print(colored(f" Label: {entry.get('label', '')}", "cyan")) print(colored(f" Words: {entry.get('words', 24)}", "cyan")) print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan")) notes = entry.get("notes", "") @@ -1786,12 +1803,14 @@ class PasswordManager: print(colored(f" Notes: {notes}", "cyan")) elif etype == EntryType.SSH.value: print(colored(" Type: SSH Key", "cyan")) + print(colored(f" Label: {entry.get('label', '')}", "cyan")) print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan")) notes = entry.get("notes", "") if notes: print(colored(f" Notes: {notes}", "cyan")) elif etype == EntryType.PGP.value: print(colored(" Type: PGP Key", "cyan")) + print(colored(f" Label: {entry.get('label', '')}", "cyan")) print(colored(f" Key Type: {entry.get('key_type', 'ed25519')}", "cyan")) uid = entry.get("user_id", "") if uid: @@ -1808,11 +1827,11 @@ class PasswordManager: if notes: print(colored(f" Notes: {notes}", "cyan")) else: - website = entry.get("website", "") + website = entry.get("label", entry.get("website", "")) username = entry.get("username", "") url = entry.get("url", "") blacklisted = entry.get("blacklisted", False) - print(colored(f" Website: {website}", "cyan")) + print(colored(f" Label: {website}", "cyan")) print(colored(f" Username: {username or 'N/A'}", "cyan")) print(colored(f" URL: {url or 'N/A'}", "cyan")) print(colored(f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan")) diff --git a/src/password_manager/migrations.py b/src/password_manager/migrations.py index dbcb16a..6052e85 100644 --- a/src/password_manager/migrations.py +++ b/src/password_manager/migrations.py @@ -33,6 +33,10 @@ def _v1_to_v2(data: dict) -> dict: for k, v in passwords.items(): v.setdefault("type", "password") v.setdefault("notes", "") + if "label" not in v and "website" in v: + v["label"] = v["website"] + if v.get("type") == "password" and "website" in v: + v.pop("website", None) entries[k] = v data["entries"] = entries data["schema_version"] = 2 @@ -46,6 +50,10 @@ def _v2_to_v3(data: dict) -> dict: for entry in entries.values(): entry.setdefault("custom_fields", []) entry.setdefault("origin", "") + if entry.get("type", "password") == "password": + if "label" not in entry and "website" in entry: + entry["label"] = entry["website"] + entry.pop("website", None) data["schema_version"] = 3 return data diff --git a/src/tests/test_backup_restore.py b/src/tests/test_backup_restore.py index 1c349e1..d37281e 100644 --- a/src/tests/test_backup_restore.py +++ b/src/tests/test_backup_restore.py @@ -25,7 +25,7 @@ def test_backup_restore_workflow(monkeypatch): "schema_version": 3, "entries": { "0": { - "website": "a", + "label": "a", "length": 10, "type": "password", "kind": "password", @@ -48,7 +48,7 @@ def test_backup_restore_workflow(monkeypatch): "schema_version": 3, "entries": { "0": { - "website": "b", + "label": "b", "length": 12, "type": "password", "kind": "password", diff --git a/src/tests/test_entry_add.py b/src/tests/test_entry_add.py index d323191..f44f318 100644 --- a/src/tests/test_entry_add.py +++ b/src/tests/test_entry_add.py @@ -30,7 +30,7 @@ def test_add_and_retrieve_entry(): entry = entry_mgr.retrieve_entry(index) assert entry == { - "website": "example.com", + "label": "example.com", "length": 12, "username": "user", "url": "", @@ -69,9 +69,9 @@ def test_round_trip_entry_types(method, expected_type): index = 0 else: if method == "add_ssh_key": - index = entry_mgr.add_ssh_key(TEST_SEED) + index = entry_mgr.add_ssh_key("ssh", TEST_SEED) elif method == "add_seed": - index = entry_mgr.add_seed(TEST_SEED) + index = entry_mgr.add_seed("seed", TEST_SEED) else: index = getattr(entry_mgr, method)() diff --git a/src/tests/test_index_import_export.py b/src/tests/test_index_import_export.py index 5742cf3..d6fa622 100644 --- a/src/tests/test_index_import_export.py +++ b/src/tests/test_index_import_export.py @@ -34,7 +34,7 @@ def test_index_export_import_round_trip(): "schema_version": 3, "entries": { "0": { - "website": "example", + "label": "example", "type": "password", "notes": "", "custom_fields": [], @@ -52,7 +52,7 @@ def test_index_export_import_round_trip(): "schema_version": 3, "entries": { "0": { - "website": "changed", + "label": "changed", "type": "password", "notes": "", "custom_fields": [], diff --git a/src/tests/test_migrations.py b/src/tests/test_migrations.py index 87106cc..4fb2f11 100644 --- a/src/tests/test_migrations.py +++ b/src/tests/test_migrations.py @@ -20,7 +20,7 @@ def test_migrate_v0_to_v3(tmp_path: Path): data = vault.load_index() assert data["schema_version"] == LATEST_VERSION expected_entry = { - "website": "a", + "label": "a", "length": 8, "type": "password", "notes": "", @@ -37,7 +37,7 @@ def test_migrate_v1_to_v3(tmp_path: Path): data = vault.load_index() assert data["schema_version"] == LATEST_VERSION expected_entry = { - "website": "b", + "label": "b", "length": 10, "type": "password", "notes": "", @@ -59,7 +59,7 @@ def test_migrate_v2_to_v3(tmp_path: Path): data = vault.load_index() assert data["schema_version"] == LATEST_VERSION expected_entry = { - "website": "c", + "label": "c", "length": 5, "type": "password", "notes": "", diff --git a/src/tests/test_nostr_index_size.py b/src/tests/test_nostr_index_size.py index ec7acb7..442330f 100644 --- a/src/tests/test_nostr_index_size.py +++ b/src/tests/test_nostr_index_size.py @@ -58,7 +58,7 @@ def test_nostr_index_size_limits(pytestconfig: pytest.Config): if max_entries is not None and entry_count >= max_entries: break entry_mgr.add_entry( - website_name=f"site-{entry_count + 1}", + label=f"site-{entry_count + 1}", length=12, username="u" * size, url="https://example.com/" + "a" * size, diff --git a/src/tests/test_pgp_entry.py b/src/tests/test_pgp_entry.py index ee33b60..494d4d5 100644 --- a/src/tests/test_pgp_entry.py +++ b/src/tests/test_pgp_entry.py @@ -19,7 +19,9 @@ def test_pgp_key_determinism(): backup_mgr = BackupManager(tmp_path, cfg_mgr) entry_mgr = EntryManager(vault, backup_mgr) - idx = entry_mgr.add_pgp_key(TEST_SEED, key_type="ed25519", user_id="Test") + idx = entry_mgr.add_pgp_key( + "pgp", TEST_SEED, key_type="ed25519", user_id="Test" + ) key1, fp1 = entry_mgr.get_pgp_key(idx, TEST_SEED) key2, fp2 = entry_mgr.get_pgp_key(idx, TEST_SEED) diff --git a/src/tests/test_seed_entry.py b/src/tests/test_seed_entry.py index 4d75c5b..5b43eb4 100644 --- a/src/tests/test_seed_entry.py +++ b/src/tests/test_seed_entry.py @@ -23,8 +23,8 @@ def test_seed_phrase_determinism(): backup_mgr = BackupManager(tmp_path, cfg_mgr) entry_mgr = EntryManager(vault, backup_mgr) - idx_12 = entry_mgr.add_seed(TEST_SEED, words_num=12) - idx_24 = entry_mgr.add_seed(TEST_SEED, words_num=24) + idx_12 = entry_mgr.add_seed("seed12", TEST_SEED, words_num=12) + idx_24 = entry_mgr.add_seed("seed24", TEST_SEED, words_num=24) phrase12_a = entry_mgr.get_seed_phrase(idx_12, TEST_SEED) phrase12_b = entry_mgr.get_seed_phrase(idx_12, TEST_SEED) diff --git a/src/tests/test_ssh_entry.py b/src/tests/test_ssh_entry.py index 8873691..7a2f552 100644 --- a/src/tests/test_ssh_entry.py +++ b/src/tests/test_ssh_entry.py @@ -20,9 +20,15 @@ def test_add_and_retrieve_ssh_key_pair(): backup_mgr = BackupManager(tmp_path, cfg_mgr) entry_mgr = EntryManager(vault, backup_mgr) - index = entry_mgr.add_ssh_key(TEST_SEED) + index = entry_mgr.add_ssh_key("ssh", TEST_SEED) entry = entry_mgr.retrieve_entry(index) - assert entry == {"type": "ssh", "kind": "ssh", "index": index, "notes": ""} + assert entry == { + "type": "ssh", + "kind": "ssh", + "index": index, + "label": "ssh", + "notes": "", + } priv1, pub1 = entry_mgr.get_ssh_key_pair(index, TEST_SEED) priv2, pub2 = entry_mgr.get_ssh_key_pair(index, TEST_SEED) diff --git a/src/tests/test_ssh_entry_valid.py b/src/tests/test_ssh_entry_valid.py index 42372ee..9b7ecad 100644 --- a/src/tests/test_ssh_entry_valid.py +++ b/src/tests/test_ssh_entry_valid.py @@ -21,7 +21,7 @@ def test_ssh_private_key_corresponds_to_public(): backup_mgr = BackupManager(tmp_path, cfg_mgr) entry_mgr = EntryManager(vault, backup_mgr) - idx = entry_mgr.add_ssh_key(TEST_SEED) + idx = entry_mgr.add_ssh_key("ssh", TEST_SEED) priv_pem, pub_pem = entry_mgr.get_ssh_key_pair(idx, TEST_SEED) priv_key = serialization.load_pem_private_key(