fix: migrate legacy nostr payloads

This commit is contained in:
thePR0M3TH3AN
2025-08-03 11:41:06 -04:00
parent 44ce005cdc
commit f664a6c40f
3 changed files with 64 additions and 1 deletions

View File

@@ -1255,12 +1255,14 @@ class PasswordManager:
encrypted = gzip.decompress(b"".join(chunks)) encrypted = gzip.decompress(b"".join(chunks))
current = self.vault.get_encrypted_index() current = self.vault.get_encrypted_index()
updated = False updated = False
migrated = False
if current != encrypted: if current != encrypted:
if self.vault.decrypt_and_save_index_from_nostr( if self.vault.decrypt_and_save_index_from_nostr(
encrypted, strict=False, merge=False encrypted, strict=False, merge=False
): ):
updated = True updated = True
current = encrypted current = encrypted
migrated = migrated or self.vault.migrated_from_legacy
if manifest.delta_since: if manifest.delta_since:
version = int(manifest.delta_since) version = int(manifest.delta_since)
deltas = await self.nostr_client.fetch_deltas_since(version) deltas = await self.nostr_client.fetch_deltas_since(version)
@@ -1271,6 +1273,9 @@ class PasswordManager:
): ):
updated = True updated = True
current = delta current = delta
migrated = migrated or self.vault.migrated_from_legacy
if migrated and not getattr(self, "offline_mode", False):
self.start_background_vault_sync()
if updated: if updated:
logger.info("Local database synchronized from Nostr.") logger.info("Local database synchronized from Nostr.")
except Exception as e: except Exception as e:
@@ -1403,11 +1408,13 @@ class PasswordManager:
if result: if result:
manifest, chunks = result manifest, chunks = result
encrypted = gzip.decompress(b"".join(chunks)) encrypted = gzip.decompress(b"".join(chunks))
migrated = False
success = self.vault.decrypt_and_save_index_from_nostr( success = self.vault.decrypt_and_save_index_from_nostr(
encrypted, strict=False, merge=False encrypted, strict=False, merge=False
) )
if success: if success:
have_data = True have_data = True
migrated = migrated or self.vault.migrated_from_legacy
current = encrypted current = encrypted
if manifest.delta_since: if manifest.delta_since:
version = int(manifest.delta_since) version = int(manifest.delta_since)
@@ -1418,6 +1425,11 @@ class PasswordManager:
delta, strict=False, merge=True delta, strict=False, merge=True
): ):
current = delta current = delta
migrated = (
migrated or self.vault.migrated_from_legacy
)
if migrated and not getattr(self, "offline_mode", False):
self.start_background_vault_sync()
logger.info("Initialized local database from Nostr.") logger.info("Initialized local database from Nostr.")
except Exception as e: # pragma: no cover - network errors except Exception as e: # pragma: no cover - network errors
logger.warning(f"Unable to sync index from Nostr: {e}") logger.warning(f"Unable to sync index from Nostr: {e}")

View File

@@ -25,6 +25,7 @@ class Vault:
self.fingerprint_dir = Path(fingerprint_dir) self.fingerprint_dir = Path(fingerprint_dir)
self.index_file = self.fingerprint_dir / self.INDEX_FILENAME self.index_file = self.fingerprint_dir / self.INDEX_FILENAME
self.config_file = self.fingerprint_dir / self.CONFIG_FILENAME self.config_file = self.fingerprint_dir / self.CONFIG_FILENAME
self.migrated_from_legacy = False
def set_encryption_manager(self, manager: EncryptionManager) -> None: def set_encryption_manager(self, manager: EncryptionManager) -> None:
"""Replace the internal encryption manager.""" """Replace the internal encryption manager."""
@@ -97,9 +98,13 @@ class Vault:
self, encrypted_data: bytes, *, strict: bool = True, merge: bool = False self, encrypted_data: bytes, *, strict: bool = True, merge: bool = False
) -> bool: ) -> bool:
"""Decrypt Nostr payload and update the local index.""" """Decrypt Nostr payload and update the local index."""
return self.encryption_manager.decrypt_and_save_index_from_nostr( self.migrated_from_legacy = not encrypted_data.startswith(b"V2:")
result = self.encryption_manager.decrypt_and_save_index_from_nostr(
encrypted_data, strict=strict, merge=merge encrypted_data, strict=strict, merge=merge
) )
if not result:
self.migrated_from_legacy = False
return result
# ----- Config helpers ----- # ----- Config helpers -----
def load_config(self) -> dict: def load_config(self) -> dict:

View File

@@ -6,6 +6,8 @@ from helpers import create_vault, TEST_SEED, TEST_PASSWORD
from utils.key_derivation import derive_index_key from utils.key_derivation import derive_index_key
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from types import SimpleNamespace from types import SimpleNamespace
import asyncio
import gzip
from seedpass.core.manager import PasswordManager, EncryptionMode from seedpass.core.manager import PasswordManager, EncryptionMode
from seedpass.core.vault import Vault from seedpass.core.vault import Vault
@@ -81,3 +83,47 @@ def test_migration_triggers_sync(monkeypatch, tmp_path: Path):
pm.initialize_managers() pm.initialize_managers()
assert calls["sync"] == 1 assert calls["sync"] == 1
def test_legacy_nostr_payload_triggers_sync(monkeypatch, tmp_path: Path):
vault, enc_mgr = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD)
key = derive_index_key(TEST_SEED)
data = {"schema_version": 4, "entries": {}}
legacy_enc = Fernet(key).encrypt(json.dumps(data).encode())
compressed = gzip.compress(legacy_enc)
class DummyClient:
def __init__(self):
self.relays = []
self.last_error = None
self.fingerprint = None
async def fetch_latest_snapshot(self):
from nostr.backup_models import Manifest
return Manifest(ver=1, algo="gzip", chunks=[], delta_since=None), [
compressed
]
async def fetch_deltas_since(self, version):
return []
pm = PasswordManager.__new__(PasswordManager)
pm.encryption_mode = EncryptionMode.SEED_ONLY
pm.encryption_manager = enc_mgr
pm.vault = Vault(enc_mgr, tmp_path)
pm.parent_seed = TEST_SEED
pm.fingerprint_dir = tmp_path
pm.current_fingerprint = tmp_path.name
pm.nostr_client = DummyClient()
pm.offline_mode = False
calls = {"sync": 0}
pm.start_background_vault_sync = lambda *a, **k: calls.__setitem__(
"sync", calls["sync"] + 1
)
asyncio.run(pm.sync_index_from_nostr_async())
assert calls["sync"] == 1
assert pm.vault.load_index() == data