mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
Merge pull request #626 from PR0M3TH3AN/codex/refactor-password-handling-in-manager.py
Refactor manager input arguments
This commit is contained in:
@@ -462,8 +462,9 @@ def vault_reveal_parent_seed(
|
||||
) -> None:
|
||||
"""Display the parent seed and optionally write an encrypted backup file."""
|
||||
vault_service, _profile, _sync = _get_services(ctx)
|
||||
password = typer.prompt("Master password", hide_input=True)
|
||||
vault_service.backup_parent_seed(
|
||||
BackupParentSeedRequest(path=Path(file) if file else None)
|
||||
BackupParentSeedRequest(path=Path(file) if file else None, password=password)
|
||||
)
|
||||
|
||||
|
||||
@@ -630,7 +631,10 @@ def fingerprint_remove(ctx: typer.Context, fingerprint: str) -> None:
|
||||
def fingerprint_switch(ctx: typer.Context, fingerprint: str) -> None:
|
||||
"""Switch to another seed profile."""
|
||||
_vault, profile_service, _sync = _get_services(ctx)
|
||||
profile_service.switch_profile(ProfileSwitchRequest(fingerprint=fingerprint))
|
||||
password = typer.prompt("Master password", hide_input=True)
|
||||
profile_service.switch_profile(
|
||||
ProfileSwitchRequest(fingerprint=fingerprint, password=password)
|
||||
)
|
||||
|
||||
|
||||
@util_app.command("generate-password")
|
||||
|
@@ -57,12 +57,14 @@ class BackupParentSeedRequest(BaseModel):
|
||||
"""Optional path to write the encrypted seed backup."""
|
||||
|
||||
path: Optional[Path] = None
|
||||
password: Optional[str] = None
|
||||
|
||||
|
||||
class ProfileSwitchRequest(BaseModel):
|
||||
"""Select a different seed profile."""
|
||||
|
||||
fingerprint: str
|
||||
password: Optional[str] = None
|
||||
|
||||
|
||||
class ProfileRemoveRequest(BaseModel):
|
||||
@@ -123,7 +125,9 @@ class VaultService:
|
||||
"""Backup and reveal the parent seed."""
|
||||
|
||||
with self._lock:
|
||||
self._manager.handle_backup_reveal_parent_seed(req.path)
|
||||
self._manager.handle_backup_reveal_parent_seed(
|
||||
req.path, password=req.password
|
||||
)
|
||||
|
||||
def stats(self) -> Dict:
|
||||
"""Return statistics about the current profile."""
|
||||
@@ -164,7 +168,7 @@ class ProfileService:
|
||||
"""Switch to ``req.fingerprint``."""
|
||||
|
||||
with self._lock:
|
||||
self._manager.select_fingerprint(req.fingerprint)
|
||||
self._manager.select_fingerprint(req.fingerprint, password=req.password)
|
||||
|
||||
|
||||
class SyncService:
|
||||
|
@@ -546,7 +546,7 @@ class PasswordManager:
|
||||
print(colored(f"Error: Failed to load parent seed: {e}", "red"))
|
||||
sys.exit(1)
|
||||
|
||||
def handle_switch_fingerprint(self) -> bool:
|
||||
def handle_switch_fingerprint(self, *, password: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Handles switching to a different seed profile.
|
||||
|
||||
@@ -587,9 +587,10 @@ class PasswordManager:
|
||||
return False # Return False to indicate failure
|
||||
|
||||
# Prompt for master password for the selected seed profile
|
||||
password = prompt_existing_password(
|
||||
"Enter the master password for the selected seed profile: "
|
||||
)
|
||||
if password is None:
|
||||
password = prompt_existing_password(
|
||||
"Enter the master password for the selected seed profile: "
|
||||
)
|
||||
|
||||
# Set up the encryption manager with the new password and seed profile directory
|
||||
if not self.setup_encryption_manager(
|
||||
@@ -676,14 +677,14 @@ class PasswordManager:
|
||||
self.update_activity()
|
||||
self.start_background_sync()
|
||||
|
||||
def handle_existing_seed(self) -> None:
|
||||
def handle_existing_seed(self, *, password: Optional[str] = None) -> None:
|
||||
"""
|
||||
Handles the scenario where an existing parent seed file is found.
|
||||
Prompts the user for the master password to decrypt the seed.
|
||||
"""
|
||||
try:
|
||||
# Prompt for password using masked input
|
||||
password = prompt_existing_password("Enter your login password: ")
|
||||
if password is None:
|
||||
password = prompt_existing_password("Enter your login password: ")
|
||||
|
||||
# Derive encryption key from password
|
||||
iterations = (
|
||||
@@ -778,7 +779,11 @@ class PasswordManager:
|
||||
sys.exit(1)
|
||||
|
||||
def setup_existing_seed(
|
||||
self, method: Literal["paste", "words"] = "paste"
|
||||
self,
|
||||
method: Literal["paste", "words"] = "paste",
|
||||
*,
|
||||
seed: Optional[str] = None,
|
||||
password: Optional[str] = None,
|
||||
) -> Optional[str]:
|
||||
"""Prompt for an existing BIP-85 seed and set it up.
|
||||
|
||||
@@ -794,7 +799,9 @@ class PasswordManager:
|
||||
The fingerprint if setup is successful, ``None`` otherwise.
|
||||
"""
|
||||
try:
|
||||
if method == "words":
|
||||
if seed is not None:
|
||||
parent_seed = seed
|
||||
elif method == "words":
|
||||
parent_seed = prompt_seed_words()
|
||||
else:
|
||||
parent_seed = masked_input("Enter your 12-word BIP-85 seed: ").strip()
|
||||
@@ -804,17 +811,21 @@ class PasswordManager:
|
||||
print(colored("Error: Invalid BIP-85 seed phrase.", "red"))
|
||||
sys.exit(1)
|
||||
|
||||
return self._finalize_existing_seed(parent_seed)
|
||||
return self._finalize_existing_seed(parent_seed, password=password)
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Operation cancelled by user.")
|
||||
self.notify("Operation cancelled by user.", level="WARNING")
|
||||
sys.exit(0)
|
||||
|
||||
def setup_existing_seed_word_by_word(self) -> Optional[str]:
|
||||
def setup_existing_seed_word_by_word(
|
||||
self, *, seed: Optional[str] = None, password: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
"""Prompt for an existing seed one word at a time and set it up."""
|
||||
return self.setup_existing_seed(method="words")
|
||||
return self.setup_existing_seed(method="words", seed=seed, password=password)
|
||||
|
||||
def _finalize_existing_seed(self, parent_seed: str) -> Optional[str]:
|
||||
def _finalize_existing_seed(
|
||||
self, parent_seed: str, *, password: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
"""Common logic for initializing an existing seed."""
|
||||
if self.validate_bip85_seed(parent_seed):
|
||||
fingerprint = self.fingerprint_manager.add_fingerprint(parent_seed)
|
||||
@@ -842,7 +853,8 @@ class PasswordManager:
|
||||
logging.info(f"Current seed profile set to {fingerprint}")
|
||||
|
||||
try:
|
||||
password = prompt_for_password()
|
||||
if password is None:
|
||||
password = prompt_for_password()
|
||||
index_key = derive_index_key(parent_seed)
|
||||
iterations = (
|
||||
self.config_manager.get_kdf_iterations()
|
||||
@@ -976,7 +988,9 @@ class PasswordManager:
|
||||
print(colored(f"Error: Failed to generate BIP-85 seed: {e}", "red"))
|
||||
sys.exit(1)
|
||||
|
||||
def save_and_encrypt_seed(self, seed: str, fingerprint_dir: Path) -> None:
|
||||
def save_and_encrypt_seed(
|
||||
self, seed: str, fingerprint_dir: Path, *, password: Optional[str] = None
|
||||
) -> None:
|
||||
"""
|
||||
Saves and encrypts the parent seed.
|
||||
|
||||
@@ -988,8 +1002,8 @@ class PasswordManager:
|
||||
# Set self.fingerprint_dir
|
||||
self.fingerprint_dir = fingerprint_dir
|
||||
|
||||
# Prompt for password
|
||||
password = prompt_for_password()
|
||||
if password is None:
|
||||
password = prompt_for_password()
|
||||
|
||||
index_key = derive_index_key(seed)
|
||||
iterations = (
|
||||
@@ -3732,7 +3746,9 @@ class PasswordManager:
|
||||
print(colored(f"Error: Failed to export 2FA codes: {e}", "red"))
|
||||
return None
|
||||
|
||||
def handle_backup_reveal_parent_seed(self, file: Path | None = None) -> None:
|
||||
def handle_backup_reveal_parent_seed(
|
||||
self, file: Path | None = None, *, password: Optional[str] = None
|
||||
) -> None:
|
||||
"""Reveal the parent seed and optionally save an encrypted backup.
|
||||
|
||||
Parameters
|
||||
@@ -3762,9 +3778,10 @@ class PasswordManager:
|
||||
)
|
||||
|
||||
# Verify user's identity with secure password verification
|
||||
password = prompt_existing_password(
|
||||
"Enter your master password to continue: "
|
||||
)
|
||||
if password is None:
|
||||
password = prompt_existing_password(
|
||||
"Enter your master password to continue: "
|
||||
)
|
||||
if not self.verify_password(password):
|
||||
print(colored("Incorrect password. Operation aborted.", "red"))
|
||||
return
|
||||
|
@@ -22,9 +22,6 @@ def test_switch_fingerprint_triggers_bg_sync(monkeypatch, tmp_path):
|
||||
pm.config_manager = SimpleNamespace(get_quick_unlock=lambda: False)
|
||||
|
||||
monkeypatch.setattr("builtins.input", lambda *_a, **_k: "1")
|
||||
monkeypatch.setattr(
|
||||
"seedpass.core.manager.prompt_existing_password", lambda *_a, **_k: "pw"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
PasswordManager, "setup_encryption_manager", lambda *a, **k: True
|
||||
)
|
||||
@@ -39,7 +36,7 @@ def test_switch_fingerprint_triggers_bg_sync(monkeypatch, tmp_path):
|
||||
|
||||
monkeypatch.setattr(PasswordManager, "start_background_sync", fake_bg)
|
||||
|
||||
assert pm.handle_switch_fingerprint()
|
||||
assert pm.handle_switch_fingerprint(password="pw")
|
||||
assert calls["count"] == 1
|
||||
|
||||
|
||||
|
@@ -43,7 +43,7 @@ class DummyPM:
|
||||
self.change_password = lambda *a, **kw: None
|
||||
self.lock_vault = lambda: None
|
||||
self.get_profile_stats = lambda: {"n": 1}
|
||||
self.handle_backup_reveal_parent_seed = lambda path=None: None
|
||||
self.handle_backup_reveal_parent_seed = lambda path=None, **_: None
|
||||
self.handle_verify_checksum = lambda: None
|
||||
self.handle_update_script_checksum = lambda: None
|
||||
self.add_new_fingerprint = lambda: None
|
||||
@@ -76,7 +76,7 @@ class DummyPM:
|
||||
)
|
||||
self.secret_mode_enabled = True
|
||||
self.clipboard_clear_delay = 30
|
||||
self.select_fingerprint = lambda fp: None
|
||||
self.select_fingerprint = lambda fp, **_: None
|
||||
|
||||
|
||||
def load_doc_commands() -> list[str]:
|
||||
|
@@ -36,7 +36,7 @@ def test_setup_existing_seed_words(monkeypatch):
|
||||
monkeypatch.setattr(builtins, "input", lambda *_: "y")
|
||||
|
||||
pm = PasswordManager.__new__(PasswordManager)
|
||||
monkeypatch.setattr(pm, "_finalize_existing_seed", lambda seed: seed)
|
||||
monkeypatch.setattr(pm, "_finalize_existing_seed", lambda seed, **_: seed)
|
||||
|
||||
result = pm.setup_existing_seed(method="words")
|
||||
assert result == phrase
|
||||
@@ -60,8 +60,32 @@ def test_setup_existing_seed_paste(monkeypatch):
|
||||
)
|
||||
|
||||
pm = PasswordManager.__new__(PasswordManager)
|
||||
monkeypatch.setattr(pm, "_finalize_existing_seed", lambda seed: seed)
|
||||
monkeypatch.setattr(pm, "_finalize_existing_seed", lambda seed, **_: seed)
|
||||
|
||||
result = pm.setup_existing_seed(method="paste")
|
||||
assert result == phrase
|
||||
assert called["prompt"].startswith("Enter your 12-word BIP-85 seed")
|
||||
|
||||
|
||||
def test_setup_existing_seed_with_args(monkeypatch):
|
||||
m = Mnemonic("english")
|
||||
phrase = m.generate(strength=128)
|
||||
|
||||
called = {}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"seedpass.core.manager.masked_input",
|
||||
lambda *_: (_ for _ in ()).throw(RuntimeError("prompt")),
|
||||
)
|
||||
|
||||
def finalize(seed, *, password=None):
|
||||
called["seed"] = seed
|
||||
called["pw"] = password
|
||||
return "fp"
|
||||
|
||||
pm = PasswordManager.__new__(PasswordManager)
|
||||
monkeypatch.setattr(pm, "_finalize_existing_seed", finalize)
|
||||
result = pm.setup_existing_seed(method="paste", seed=phrase, password="pw")
|
||||
assert result == "fp"
|
||||
assert called["seed"] == phrase
|
||||
assert called["pw"] == "pw"
|
||||
|
@@ -24,9 +24,6 @@ def _make_pm(tmp_path: Path) -> PasswordManager:
|
||||
def test_handle_backup_reveal_parent_seed_confirm(monkeypatch, tmp_path, capsys):
|
||||
pm = _make_pm(tmp_path)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"seedpass.core.manager.prompt_existing_password", lambda *_: "pw"
|
||||
)
|
||||
confirms = iter([True, True])
|
||||
monkeypatch.setattr(
|
||||
"seedpass.core.manager.confirm_action", lambda *_a, **_k: next(confirms)
|
||||
@@ -39,7 +36,7 @@ def test_handle_backup_reveal_parent_seed_confirm(monkeypatch, tmp_path, capsys)
|
||||
pm.encryption_manager = SimpleNamespace(encrypt_and_save_file=fake_save)
|
||||
monkeypatch.setattr(builtins, "input", lambda *_: "mybackup.enc")
|
||||
|
||||
pm.handle_backup_reveal_parent_seed()
|
||||
pm.handle_backup_reveal_parent_seed(password="pw")
|
||||
out = capsys.readouterr().out
|
||||
|
||||
assert "seed phrase" in out
|
||||
@@ -50,16 +47,13 @@ def test_handle_backup_reveal_parent_seed_confirm(monkeypatch, tmp_path, capsys)
|
||||
def test_handle_backup_reveal_parent_seed_cancel(monkeypatch, tmp_path, capsys):
|
||||
pm = _make_pm(tmp_path)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"seedpass.core.manager.prompt_existing_password", lambda *_: "pw"
|
||||
)
|
||||
monkeypatch.setattr("seedpass.core.manager.confirm_action", lambda *_a, **_k: False)
|
||||
saved = []
|
||||
pm.encryption_manager = SimpleNamespace(
|
||||
encrypt_and_save_file=lambda data, path: saved.append((data, path))
|
||||
)
|
||||
|
||||
pm.handle_backup_reveal_parent_seed()
|
||||
pm.handle_backup_reveal_parent_seed(password="pw")
|
||||
out = capsys.readouterr().out
|
||||
|
||||
assert "seed phrase" not in out
|
||||
|
@@ -31,10 +31,6 @@ def test_add_and_switch_fingerprint(monkeypatch):
|
||||
pm.current_fingerprint = None
|
||||
|
||||
monkeypatch.setattr("builtins.input", lambda *_args, **_kwargs: "1")
|
||||
monkeypatch.setattr(
|
||||
"seedpass.core.manager.prompt_existing_password",
|
||||
lambda *_a, **_k: "pass",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
PasswordManager,
|
||||
"setup_encryption_manager",
|
||||
@@ -50,7 +46,7 @@ def test_add_and_switch_fingerprint(monkeypatch):
|
||||
"seedpass.core.manager.NostrClient", lambda *a, **kw: object()
|
||||
)
|
||||
|
||||
assert pm.handle_switch_fingerprint()
|
||||
assert pm.handle_switch_fingerprint(password="pass")
|
||||
assert pm.current_fingerprint == fingerprint
|
||||
assert fm.current_fingerprint == fingerprint
|
||||
assert pm.fingerprint_dir == expected_dir
|
||||
|
@@ -156,7 +156,7 @@ def test_vault_lock(monkeypatch):
|
||||
def test_vault_reveal_parent_seed(monkeypatch, tmp_path):
|
||||
called = {}
|
||||
|
||||
def reveal(path=None):
|
||||
def reveal(path=None, **_):
|
||||
called["path"] = path
|
||||
|
||||
pm = SimpleNamespace(
|
||||
@@ -165,7 +165,9 @@ def test_vault_reveal_parent_seed(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(cli, "PasswordManager", lambda: pm)
|
||||
out_path = tmp_path / "seed.enc"
|
||||
result = runner.invoke(
|
||||
app, ["vault", "reveal-parent-seed", "--file", str(out_path)]
|
||||
app,
|
||||
["vault", "reveal-parent-seed", "--file", str(out_path)],
|
||||
input="pw\n",
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert called["path"] == out_path
|
||||
@@ -231,14 +233,14 @@ def test_fingerprint_remove(monkeypatch):
|
||||
def test_fingerprint_switch(monkeypatch):
|
||||
called = {}
|
||||
|
||||
def switch(fp):
|
||||
def switch(fp, **_):
|
||||
called["fp"] = fp
|
||||
|
||||
pm = SimpleNamespace(
|
||||
select_fingerprint=switch, fingerprint_manager=SimpleNamespace()
|
||||
)
|
||||
monkeypatch.setattr(cli, "PasswordManager", lambda: pm)
|
||||
result = runner.invoke(app, ["fingerprint", "switch", "def"])
|
||||
result = runner.invoke(app, ["fingerprint", "switch", "def"], input="pw\n")
|
||||
assert result.exit_code == 0
|
||||
assert called.get("fp") == "def"
|
||||
|
||||
|
Reference in New Issue
Block a user