mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-07 06:48:52 +00:00
Merge pull request #700 from PR0M3TH3AN/codex/extend-handle_new_seed_setup-for-nostr-restore
Add Nostr restore option
This commit is contained in:
13
README.md
13
README.md
@@ -32,6 +32,7 @@ SeedPass now uses the `portalocker` library for cross-platform file locking. No
|
||||
- [Running the Application](#running-the-application)
|
||||
- [Managing Multiple Seeds](#managing-multiple-seeds)
|
||||
- [Additional Entry Types](#additional-entry-types)
|
||||
- [Recovery](#recovery)
|
||||
- [Building a standalone executable](#building-a-standalone-executable)
|
||||
- [Packaging with Briefcase](#packaging-with-briefcase)
|
||||
- [Security Considerations](#security-considerations)
|
||||
@@ -541,6 +542,18 @@ seedpass config set nostr_retry_delay 1
|
||||
|
||||
The default configuration uses **50,000** PBKDF2 iterations. Increase this value for stronger password hashing or lower it for faster startup (not recommended). Offline Mode skips all Nostr communication, keeping your data local until you re-enable syncing. Quick Unlock stores a hashed copy of your password in the encrypted config so that after the initial unlock, subsequent operations won't prompt for the password until you exit the program. Avoid enabling Quick Unlock on shared machines.
|
||||
|
||||
### Recovery
|
||||
|
||||
If you previously backed up your vault to Nostr you can restore it during the
|
||||
initial setup:
|
||||
|
||||
1. Start SeedPass and choose option **4** when prompted to set up a seed.
|
||||
2. Paste your BIP-85 seed phrase when asked.
|
||||
3. SeedPass initializes the profile and attempts to download the encrypted vault
|
||||
from the configured relays.
|
||||
4. A success message confirms the vault was restored. If no data is found a
|
||||
failure message is shown and a new empty vault is created.
|
||||
|
||||
## Running Tests
|
||||
|
||||
SeedPass includes a small suite of unit tests located under `src/tests`. **Before running `pytest`, be sure to install the test requirements.** Activate your virtual environment and run `pip install -r src/requirements.txt` to ensure all testing dependencies are available. Then run the tests with **pytest**. Use `-vv` to see INFO-level log messages from each passing test:
|
||||
|
@@ -808,8 +808,8 @@ class PasswordManager:
|
||||
|
||||
choice = input(
|
||||
"Do you want to (1) Paste in an existing seed in full "
|
||||
"(2) Enter an existing seed one word at a time or "
|
||||
"(3) Generate a new seed? (1/2/3): "
|
||||
"(2) Enter an existing seed one word at a time, "
|
||||
"(3) Generate a new seed, or (4) Restore from Nostr? (1/2/3/4): "
|
||||
).strip()
|
||||
|
||||
if choice == "1":
|
||||
@@ -818,6 +818,15 @@ class PasswordManager:
|
||||
self.setup_existing_seed(method="words")
|
||||
elif choice == "3":
|
||||
self.generate_new_seed()
|
||||
elif choice == "4":
|
||||
fp = self.setup_existing_seed()
|
||||
if fp:
|
||||
success = self.attempt_initial_sync()
|
||||
if success:
|
||||
print(colored("Vault restored from Nostr.", "green"))
|
||||
else:
|
||||
print(colored("Failed to download vault from Nostr.", "red"))
|
||||
return
|
||||
else:
|
||||
print(colored("Invalid choice. Exiting.", "red"))
|
||||
sys.exit(1)
|
||||
|
74
src/tests/test_restore_from_nostr_setup.py
Normal file
74
src/tests/test_restore_from_nostr_setup.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from pathlib import Path
|
||||
|
||||
from helpers import create_vault, dummy_nostr_client, TEST_SEED, TEST_PASSWORD
|
||||
|
||||
from seedpass.core.entry_management import EntryManager
|
||||
from seedpass.core.backup import BackupManager
|
||||
from seedpass.core.config_manager import ConfigManager
|
||||
from seedpass.core.manager import PasswordManager, EncryptionMode
|
||||
|
||||
|
||||
def _init_pm(dir_path: Path, client) -> PasswordManager:
|
||||
vault, enc_mgr = create_vault(dir_path, TEST_SEED, TEST_PASSWORD)
|
||||
cfg_mgr = ConfigManager(vault, dir_path)
|
||||
backup_mgr = BackupManager(dir_path, cfg_mgr)
|
||||
entry_mgr = EntryManager(vault, backup_mgr)
|
||||
|
||||
pm = PasswordManager.__new__(PasswordManager)
|
||||
pm.encryption_mode = EncryptionMode.SEED_ONLY
|
||||
pm.encryption_manager = enc_mgr
|
||||
pm.vault = vault
|
||||
pm.entry_manager = entry_mgr
|
||||
pm.backup_manager = backup_mgr
|
||||
pm.config_manager = cfg_mgr
|
||||
pm.nostr_client = client
|
||||
pm.fingerprint_dir = dir_path
|
||||
pm.current_fingerprint = "fp"
|
||||
pm.is_dirty = False
|
||||
return pm
|
||||
|
||||
|
||||
def test_handle_new_seed_setup_restore_from_nostr(monkeypatch, tmp_path, capsys):
|
||||
client, _relay = dummy_nostr_client.__wrapped__(tmp_path / "srv", monkeypatch)
|
||||
|
||||
dir_a = tmp_path / "A"
|
||||
dir_b = tmp_path / "B"
|
||||
dir_a.mkdir()
|
||||
dir_b.mkdir()
|
||||
|
||||
pm_src = _init_pm(dir_a, client)
|
||||
pm_src.notify = lambda *a, **k: None
|
||||
pm_src.entry_manager.add_entry("site1", 12)
|
||||
pm_src.sync_vault()
|
||||
|
||||
pm_new = PasswordManager.__new__(PasswordManager)
|
||||
pm_new.encryption_mode = EncryptionMode.SEED_ONLY
|
||||
pm_new.nostr_client = client
|
||||
pm_new.notify = lambda *a, **k: None
|
||||
|
||||
def finalize(seed, *, password=None):
|
||||
vault, enc_mgr = create_vault(dir_b, seed, TEST_PASSWORD)
|
||||
cfg_mgr = ConfigManager(vault, dir_b)
|
||||
backup_mgr = BackupManager(dir_b, cfg_mgr)
|
||||
entry_mgr = EntryManager(vault, backup_mgr)
|
||||
pm_new.encryption_manager = enc_mgr
|
||||
pm_new.vault = vault
|
||||
pm_new.entry_manager = entry_mgr
|
||||
pm_new.backup_manager = backup_mgr
|
||||
pm_new.config_manager = cfg_mgr
|
||||
pm_new.fingerprint_dir = dir_b
|
||||
pm_new.current_fingerprint = "fp"
|
||||
pm_new.nostr_client = client
|
||||
return "fp"
|
||||
|
||||
monkeypatch.setattr(pm_new, "_finalize_existing_seed", finalize)
|
||||
monkeypatch.setattr("seedpass.core.manager.masked_input", lambda *_: TEST_SEED)
|
||||
|
||||
inputs = iter(["4"])
|
||||
monkeypatch.setattr("builtins.input", lambda *a, **k: next(inputs))
|
||||
|
||||
pm_new.handle_new_seed_setup()
|
||||
out = capsys.readouterr().out
|
||||
assert "Vault restored from Nostr" in out
|
||||
labels = [e[1] for e in pm_new.entry_manager.list_entries()]
|
||||
assert labels == ["site1"]
|
Reference in New Issue
Block a user