mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
Add portable export/import features
This commit is contained in:
37
src/main.py
37
src/main.py
@@ -13,6 +13,7 @@ from termcolor import colored
|
||||
import traceback
|
||||
|
||||
from password_manager.manager import PasswordManager
|
||||
from password_manager.portable_backup import PortableMode
|
||||
from nostr.client import NostrClient
|
||||
from constants import INACTIVITY_TIMEOUT
|
||||
from utils.key_derivation import EncryptionMode
|
||||
@@ -457,8 +458,10 @@ def handle_settings(password_manager: PasswordManager) -> None:
|
||||
print("3. Change password")
|
||||
print("4. Verify Script Checksum")
|
||||
print("5. Backup Parent Seed")
|
||||
print("6. Lock Vault")
|
||||
print("7. Back")
|
||||
print("6. Export database")
|
||||
print("7. Import database")
|
||||
print("8. Lock Vault")
|
||||
print("9. Back")
|
||||
choice = input("Select an option: ").strip()
|
||||
if choice == "1":
|
||||
handle_profiles_menu(password_manager)
|
||||
@@ -471,10 +474,16 @@ def handle_settings(password_manager: PasswordManager) -> None:
|
||||
elif choice == "5":
|
||||
password_manager.handle_backup_reveal_parent_seed()
|
||||
elif choice == "6":
|
||||
password_manager.handle_export_database()
|
||||
elif choice == "7":
|
||||
path = input("Enter path to backup file: ").strip()
|
||||
if path:
|
||||
password_manager.handle_import_database(Path(path))
|
||||
elif choice == "8":
|
||||
password_manager.lock_vault()
|
||||
print(colored("Vault locked. Please re-enter your password.", "yellow"))
|
||||
password_manager.unlock_vault()
|
||||
elif choice == "7":
|
||||
elif choice == "9":
|
||||
break
|
||||
else:
|
||||
print(colored("Invalid choice.", "red"))
|
||||
@@ -565,11 +574,25 @@ if __name__ == "__main__":
|
||||
# Load config from disk and parse command-line arguments
|
||||
cfg = load_global_config()
|
||||
parser = argparse.ArgumentParser()
|
||||
sub = parser.add_subparsers(dest="command")
|
||||
|
||||
parser.add_argument(
|
||||
"--encryption-mode",
|
||||
choices=[m.value for m in EncryptionMode],
|
||||
help="Select encryption mode",
|
||||
)
|
||||
|
||||
exp = sub.add_parser("export")
|
||||
exp.add_argument(
|
||||
"--mode",
|
||||
choices=[m.value for m in PortableMode],
|
||||
default=PortableMode.SEED_ONLY.value,
|
||||
)
|
||||
exp.add_argument("--file")
|
||||
|
||||
imp = sub.add_parser("import")
|
||||
imp.add_argument("--file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
mode_value = cfg.get("encryption_mode", EncryptionMode.SEED_ONLY.value)
|
||||
@@ -591,6 +614,14 @@ if __name__ == "__main__":
|
||||
print(colored(f"Error: Failed to initialize PasswordManager: {e}", "red"))
|
||||
sys.exit(1)
|
||||
|
||||
if args.command == "export":
|
||||
mode = PortableMode(args.mode)
|
||||
password_manager.handle_export_database(mode, Path(args.file))
|
||||
sys.exit(0)
|
||||
elif args.command == "import":
|
||||
password_manager.handle_import_database(Path(args.file))
|
||||
sys.exit(0)
|
||||
|
||||
# Register signal handlers for graceful shutdown
|
||||
def signal_handler(sig, frame):
|
||||
"""
|
||||
|
@@ -24,6 +24,11 @@ from password_manager.entry_management import EntryManager
|
||||
from password_manager.password_generation import PasswordGenerator
|
||||
from password_manager.backup import BackupManager
|
||||
from password_manager.vault import Vault
|
||||
from password_manager.portable_backup import (
|
||||
export_backup,
|
||||
import_backup,
|
||||
PortableMode,
|
||||
)
|
||||
from utils.key_derivation import (
|
||||
derive_key_from_parent_seed,
|
||||
derive_key_from_password,
|
||||
@@ -1137,6 +1142,35 @@ class PasswordManager:
|
||||
logging.error(f"Failed to restore backup: {e}", exc_info=True)
|
||||
print(colored(f"Error: Failed to restore backup: {e}", "red"))
|
||||
|
||||
def handle_export_database(
|
||||
self,
|
||||
mode: "PortableMode" = PortableMode.SEED_ONLY,
|
||||
dest: Path | None = None,
|
||||
) -> Path | None:
|
||||
"""Export the current database to an encrypted portable file."""
|
||||
try:
|
||||
path = export_backup(
|
||||
self.vault,
|
||||
self.backup_manager,
|
||||
mode,
|
||||
dest,
|
||||
)
|
||||
print(colored(f"Database exported to '{path}'.", "green"))
|
||||
return path
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to export database: {e}", exc_info=True)
|
||||
print(colored(f"Error: Failed to export database: {e}", "red"))
|
||||
return None
|
||||
|
||||
def handle_import_database(self, src: Path) -> None:
|
||||
"""Import a portable database file, replacing the current index."""
|
||||
try:
|
||||
import_backup(self.vault, self.backup_manager, src)
|
||||
print(colored("Database imported successfully.", "green"))
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to import database: {e}", exc_info=True)
|
||||
print(colored(f"Error: Failed to import database: {e}", "red"))
|
||||
|
||||
def handle_backup_reveal_parent_seed(self) -> None:
|
||||
"""
|
||||
Handles the backup and reveal of the parent seed.
|
||||
|
47
src/tests/test_cli_portable_backup_commands.py
Normal file
47
src/tests/test_cli_portable_backup_commands.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import runpy
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
import main
|
||||
from password_manager.portable_backup import PortableMode
|
||||
from password_manager.manager import PasswordManager
|
||||
|
||||
|
||||
def _run(argv, monkeypatch):
|
||||
monkeypatch.setattr(sys, "argv", ["seedpass"] + argv)
|
||||
monkeypatch.setattr(main, "load_global_config", lambda: {})
|
||||
called = {}
|
||||
|
||||
def fake_init(self, encryption_mode):
|
||||
called["init"] = True
|
||||
|
||||
def fake_export(self, mode, dest):
|
||||
called["export"] = (mode, Path(dest))
|
||||
|
||||
def fake_import(self, src):
|
||||
called["import"] = Path(src)
|
||||
|
||||
monkeypatch.setattr(PasswordManager, "__init__", fake_init)
|
||||
monkeypatch.setattr(PasswordManager, "handle_export_database", fake_export)
|
||||
monkeypatch.setattr(PasswordManager, "handle_import_database", fake_import)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
runpy.run_module("main", run_name="__main__")
|
||||
|
||||
return called
|
||||
|
||||
|
||||
def test_export_command_invokes_handler(monkeypatch):
|
||||
called = _run(["export", "--mode", "pw-only", "--file", "out.json"], monkeypatch)
|
||||
assert called["export"] == (PortableMode.PW_ONLY, Path("out.json"))
|
||||
assert "import" not in called
|
||||
|
||||
|
||||
def test_import_command_invokes_handler(monkeypatch):
|
||||
called = _run(["import", "--file", "backup.json"], monkeypatch)
|
||||
assert called["import"] == Path("backup.json")
|
||||
assert "export" not in called
|
Reference in New Issue
Block a user