diff --git a/src/seedpass/core/manager.py b/src/seedpass/core/manager.py index cabf9d4..787d2df 100644 --- a/src/seedpass/core/manager.py +++ b/src/seedpass/core/manager.py @@ -1018,7 +1018,19 @@ class PasswordManager: ) -> Optional[str]: """Common logic for initializing an existing seed.""" if self.validate_bip85_seed(parent_seed): - fingerprint = self.fingerprint_manager.add_fingerprint(parent_seed) + try: + fingerprint = self.fingerprint_manager.add_fingerprint(parent_seed) + except ValueError: + existing_fp = generate_fingerprint(parent_seed) + print(colored("Error: Seed profile already exists.", "red")) + if confirm_action("Switch to existing profile? (Y/N): "): + self.current_fingerprint = existing_fp + self.fingerprint_manager.current_fingerprint = existing_fp + self.fingerprint_dir = ( + self.fingerprint_manager.get_fingerprint_directory(existing_fp) + ) + return existing_fp + return None if not fingerprint: print( colored( @@ -1103,7 +1115,19 @@ class PasswordManager: if confirm_action("Do you want to use this generated seed? (Y/N): "): # Add a new fingerprint using the generated seed - fingerprint = self.fingerprint_manager.add_fingerprint(new_seed) + try: + fingerprint = self.fingerprint_manager.add_fingerprint(new_seed) + except ValueError: + print(colored("Error: Seed profile already exists.", "red")) + if confirm_action("Switch to existing profile? (Y/N): "): + existing_fp = generate_fingerprint(new_seed) + self.current_fingerprint = existing_fp + self.fingerprint_manager.current_fingerprint = existing_fp + self.fingerprint_dir = ( + self.fingerprint_manager.get_fingerprint_directory(existing_fp) + ) + return existing_fp + return None if not fingerprint: print( colored( @@ -1193,6 +1217,11 @@ class PasswordManager: fingerprint_dir (Path): The directory corresponding to the fingerprint. """ try: + fingerprint = generate_fingerprint(seed) + if fingerprint in self.fingerprint_manager.list_fingerprints(): + print(colored("Error: Seed profile already exists.", "red")) + raise ValueError("Fingerprint already exists") + # Set self.fingerprint_dir self.fingerprint_dir = fingerprint_dir diff --git a/src/tests/test_duplicate_seed_profile_creation.py b/src/tests/test_duplicate_seed_profile_creation.py new file mode 100644 index 0000000..0918af7 --- /dev/null +++ b/src/tests/test_duplicate_seed_profile_creation.py @@ -0,0 +1,53 @@ +import pytest +from pathlib import Path +from tempfile import TemporaryDirectory + +from seedpass.core.manager import PasswordManager +from utils.fingerprint_manager import FingerprintManager +from utils.fingerprint import generate_fingerprint + +VALID_SEED = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + + +def _setup_pm(tmp_path, monkeypatch): + pm = PasswordManager.__new__(PasswordManager) + pm.fingerprint_manager = FingerprintManager(tmp_path) + pm.config_manager = type("Cfg", (), {"get_kdf_iterations": lambda self: 1})() + monkeypatch.setattr("seedpass.core.manager.prompt_for_password", lambda: "pw") + monkeypatch.setattr("seedpass.core.manager.derive_index_key", lambda seed: b"idx") + monkeypatch.setattr( + "seedpass.core.manager.derive_key_from_password", lambda *a, **k: b"k" + ) + + class DummyEnc: + def __init__(self, *a, **k): + pass + + def encrypt_parent_seed(self, seed): + pass + + monkeypatch.setattr("seedpass.core.manager.EncryptionManager", DummyEnc) + monkeypatch.setattr("seedpass.core.manager.Vault", lambda *a, **k: object()) + monkeypatch.setattr( + "seedpass.core.manager.ConfigManager", lambda **k: pm.config_manager + ) + monkeypatch.setattr(pm, "initialize_bip85", lambda: None) + monkeypatch.setattr(pm, "initialize_managers", lambda: None) + monkeypatch.setattr(pm, "start_background_sync", lambda: None) + monkeypatch.setattr(pm, "store_hashed_password", lambda pw: None) + monkeypatch.setattr("seedpass.core.manager.confirm_action", lambda *_: False) + return pm + + +def test_duplicate_seed_profile_creation(monkeypatch): + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + pm = _setup_pm(tmp_path, monkeypatch) + + fp1 = pm._finalize_existing_seed(VALID_SEED, password="pw") + assert fp1 == generate_fingerprint(VALID_SEED) + assert pm.fingerprint_manager.list_fingerprints() == [fp1] + + fp2 = pm._finalize_existing_seed(VALID_SEED, password="pw") + assert fp2 is None + assert pm.fingerprint_manager.list_fingerprints() == [fp1] diff --git a/src/utils/fingerprint_manager.py b/src/utils/fingerprint_manager.py index 192006f..fe30f08 100644 --- a/src/utils/fingerprint_manager.py +++ b/src/utils/fingerprint_manager.py @@ -117,7 +117,8 @@ class FingerprintManager: seed_phrase (str): The BIP-39 seed phrase. Returns: - Optional[str]: The generated fingerprint or None if failed. + Optional[str]: The generated fingerprint or ``None`` if a profile + already exists or generation fails. """ fingerprint = generate_fingerprint(seed_phrase) if fingerprint and fingerprint not in self.fingerprints: @@ -133,7 +134,7 @@ class FingerprintManager: return fingerprint elif fingerprint in self.fingerprints: logger.warning(f"Fingerprint {fingerprint} already exists.") - return fingerprint + raise ValueError("Fingerprint already exists") else: logger.error("Fingerprint generation failed.") return None