Merge pull request #611 from PR0M3TH3AN/codex/add-non-interactive-password-handling

Enable non-interactive vault unlock
This commit is contained in:
thePR0M3TH3AN
2025-07-17 17:34:01 -04:00
committed by GitHub
2 changed files with 103 additions and 7 deletions

View File

@@ -117,7 +117,9 @@ class PasswordManager:
verification, ensuring the integrity and confidentiality of the stored password database.
"""
def __init__(self, fingerprint: Optional[str] = None) -> None:
def __init__(
self, fingerprint: Optional[str] = None, *, password: Optional[str] = None
) -> None:
"""Initialize the PasswordManager.
Parameters
@@ -161,7 +163,7 @@ class PasswordManager:
if fingerprint:
# Load the specified profile without prompting
self.select_fingerprint(fingerprint)
self.select_fingerprint(fingerprint, password=password)
else:
# Ensure a parent seed is set up before accessing the fingerprint directory
self.setup_parent_seed()
@@ -187,6 +189,11 @@ class PasswordManager:
)
)
@staticmethod
def get_password_prompt() -> str:
"""Return the standard prompt for requesting a master password."""
return "Enter your master password: "
@property
def parent_seed(self) -> Optional[str]:
"""Return the decrypted parent seed if set."""
@@ -269,12 +276,15 @@ class PasswordManager:
self.config_manager = None
self.locked = True
def unlock_vault(self) -> None:
"""Prompt for password and reinitialize managers."""
def unlock_vault(self, password: Optional[str] = None) -> None:
"""Unlock the vault using ``password`` without prompting if provided."""
start = time.perf_counter()
if not self.fingerprint_dir:
raise ValueError("Fingerprint directory not set")
self.setup_encryption_manager(self.fingerprint_dir)
if password is None:
self.setup_encryption_manager(self.fingerprint_dir)
else:
self.setup_encryption_manager(self.fingerprint_dir, password)
self.initialize_bip85()
self.initialize_managers()
self.locked = False
@@ -394,7 +404,9 @@ class PasswordManager:
print(colored(f"Error: Failed to add new seed profile: {e}", "red"))
sys.exit(1)
def select_fingerprint(self, fingerprint: str) -> None:
def select_fingerprint(
self, fingerprint: str, *, password: Optional[str] = None
) -> None:
if self.fingerprint_manager.select_fingerprint(fingerprint):
self.current_fingerprint = fingerprint # Add this line
self.fingerprint_dir = (
@@ -409,7 +421,7 @@ class PasswordManager:
)
sys.exit(1)
# Setup the encryption manager and load parent seed
self.setup_encryption_manager(self.fingerprint_dir)
self.setup_encryption_manager(self.fingerprint_dir, password)
# Initialize BIP85 and other managers
self.initialize_bip85()
self.initialize_managers()

View File

@@ -0,0 +1,84 @@
import importlib
import bcrypt
from pathlib import Path
from tempfile import TemporaryDirectory
import constants
import password_manager.manager as manager_module
from utils.fingerprint_manager import FingerprintManager
from password_manager.config_manager import ConfigManager
from tests.helpers import TEST_SEED, TEST_PASSWORD, create_vault
def test_init_with_password(monkeypatch):
with TemporaryDirectory() as tmpdir:
tmp = Path(tmpdir)
monkeypatch.setattr(Path, "home", lambda: tmp)
importlib.reload(constants)
importlib.reload(manager_module)
fm = FingerprintManager(constants.APP_DIR)
fp = fm.add_fingerprint(TEST_SEED)
dir_path = constants.APP_DIR / fp
vault, _enc = create_vault(dir_path, TEST_SEED, TEST_PASSWORD)
cfg = ConfigManager(vault, dir_path)
cfg.set_password_hash(
bcrypt.hashpw(TEST_PASSWORD.encode(), bcrypt.gensalt()).decode()
)
cfg.set_kdf_iterations(100_000)
called = {}
def fake_setup(self, path, pw=None, **_):
called["password"] = pw
return True
monkeypatch.setattr(
manager_module.PasswordManager, "initialize_bip85", lambda self: None
)
monkeypatch.setattr(
manager_module.PasswordManager, "initialize_managers", lambda self: None
)
monkeypatch.setattr(
manager_module.PasswordManager, "setup_encryption_manager", fake_setup
)
pm = manager_module.PasswordManager(fingerprint=fp, password=TEST_PASSWORD)
assert called["password"] == TEST_PASSWORD
def test_unlock_with_password(monkeypatch):
with TemporaryDirectory() as tmpdir:
tmp = Path(tmpdir)
monkeypatch.setattr(Path, "home", lambda: tmp)
importlib.reload(constants)
importlib.reload(manager_module)
fm = FingerprintManager(constants.APP_DIR)
fp = fm.add_fingerprint(TEST_SEED)
dir_path = constants.APP_DIR / fp
vault, _enc = create_vault(dir_path, TEST_SEED, TEST_PASSWORD)
cfg = ConfigManager(vault, dir_path)
cfg.set_password_hash(
bcrypt.hashpw(TEST_PASSWORD.encode(), bcrypt.gensalt()).decode()
)
pm = manager_module.PasswordManager.__new__(manager_module.PasswordManager)
pm.fingerprint_dir = dir_path
pm.config_manager = cfg
pm.locked = True
called = {}
def fake_setup(path, pw=None):
called["password"] = pw
monkeypatch.setattr(
manager_module.PasswordManager, "initialize_bip85", lambda self: None
)
monkeypatch.setattr(
manager_module.PasswordManager, "initialize_managers", lambda self: None
)
pm.setup_encryption_manager = fake_setup
pm.unlock_vault(TEST_PASSWORD)
assert called["password"] == TEST_PASSWORD