From 78499b267eb67e97864dd793d24280f3ab2bde33 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 13 Jul 2025 15:42:24 -0400 Subject: [PATCH] Add configurable Nostr retry settings --- README.md | 2 ++ .../01-getting-started/01-advanced_cli.md | 6 ++-- src/constants.py | 5 ++-- src/nostr/client.py | 16 +++++++++- src/password_manager/config_manager.py | 30 +++++++++++++++++++ src/seedpass/cli.py | 2 ++ src/tests/test_cli_config_set_extra.py | 2 ++ src/tests/test_cli_doc_examples.py | 2 ++ src/tests/test_config_manager.py | 15 ++++++++++ 9 files changed, 74 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a9d620c..5ecc86f 100644 --- a/README.md +++ b/README.md @@ -417,6 +417,8 @@ You can adjust these settings directly from the command line: seedpass config set kdf_iterations 200000 seedpass config set backup_interval 3600 seedpass config set quick_unlock true +seedpass config set nostr_max_retries 2 +seedpass config set nostr_retry_delay 1 ``` The default configuration uses **50,000** PBKDF2 iterations. Lower iteration counts speed up vault decryption but make brute-force attacks easier. A long backup interval means fewer backups and increases the risk of data loss. diff --git a/docs/docs/content/01-getting-started/01-advanced_cli.md b/docs/docs/content/01-getting-started/01-advanced_cli.md index 6644a02..7fa3768 100644 --- a/docs/docs/content/01-getting-started/01-advanced_cli.md +++ b/docs/docs/content/01-getting-started/01-advanced_cli.md @@ -172,8 +172,8 @@ Code: 123456 ### `config` Commands -- **`seedpass config get `** – Retrieve a configuration value such as `kdf_iterations`, `backup_interval`, `inactivity_timeout`, `secret_mode_enabled`, `clipboard_clear_delay`, `additional_backup_path`, `relays`, `quick_unlock`, or password policy fields like `min_uppercase`. -- **`seedpass config set `** – Update a configuration option. Example: `seedpass config set kdf_iterations 200000`. Use keys like `min_uppercase`, `min_lowercase`, `min_digits`, `min_special`, or `quick_unlock` to adjust settings. +- **`seedpass config get `** – Retrieve a configuration value such as `kdf_iterations`, `backup_interval`, `inactivity_timeout`, `secret_mode_enabled`, `clipboard_clear_delay`, `additional_backup_path`, `relays`, `quick_unlock`, `nostr_max_retries`, `nostr_retry_delay`, or password policy fields like `min_uppercase`. +- **`seedpass config set `** – Update a configuration option. Example: `seedpass config set kdf_iterations 200000`. Use keys like `min_uppercase`, `min_lowercase`, `min_digits`, `min_special`, `nostr_max_retries`, `nostr_retry_delay`, or `quick_unlock` to adjust settings. - **`seedpass config toggle-secret-mode`** – Interactively enable or disable Secret Mode and set the clipboard delay. - **`seedpass config toggle-offline`** – Enable or disable offline mode to skip Nostr operations. @@ -210,6 +210,6 @@ Shut down the server with `seedpass api stop`. - Use the `--help` flag for details on any command. - Set a strong master password and regularly export encrypted backups. -- Adjust configuration values like `kdf_iterations`, `backup_interval`, `inactivity_timeout`, `secret_mode_enabled`, or `quick_unlock` through the `config` commands. +- Adjust configuration values like `kdf_iterations`, `backup_interval`, `inactivity_timeout`, `secret_mode_enabled`, `nostr_max_retries`, `nostr_retry_delay`, or `quick_unlock` through the `config` commands. - Customize password complexity with `config set min_uppercase 3`, `config set min_digits 4`, and similar commands. - `entry get` is script‑friendly and can be piped into other commands. diff --git a/src/constants.py b/src/constants.py index dfcd0d1..2ebdf36 100644 --- a/src/constants.py +++ b/src/constants.py @@ -9,8 +9,9 @@ logger = logging.getLogger(__name__) # ----------------------------------- # Nostr Relay Connection Settings # ----------------------------------- -MAX_RETRIES = 3 # Maximum number of retries for relay connections -RETRY_DELAY = 5 # Seconds to wait before retrying a failed connection +# Retry fewer times with a shorter wait by default +MAX_RETRIES = 2 # Maximum number of retries for relay connections +RETRY_DELAY = 1 # Seconds to wait before retrying a failed connection MIN_HEALTHY_RELAYS = 2 # Minimum relays that should return data on startup # ----------------------------------- diff --git a/src/nostr/client.py b/src/nostr/client.py index 1268f73..915ac8f 100644 --- a/src/nostr/client.py +++ b/src/nostr/client.py @@ -27,6 +27,7 @@ from nostr_sdk import EventId, Timestamp from .key_manager import KeyManager as SeedPassKeyManager from .backup_models import Manifest, ChunkMeta, KIND_MANIFEST, KIND_SNAPSHOT_CHUNK from password_manager.encryption import EncryptionManager +from constants import MAX_RETRIES, RETRY_DELAY from utils.file_lock import exclusive_lock # Backwards compatibility for tests that patch these symbols @@ -270,11 +271,24 @@ class NostrClient: self._connected = False def retrieve_json_from_nostr_sync( - self, retries: int = 0, delay: float = 2.0 + self, retries: int | None = None, delay: float | None = None ) -> Optional[bytes]: """Retrieve the latest Kind 1 event from the author with optional retries.""" if self.offline_mode or not self.relays: return None + + if retries is None or delay is None: + from password_manager.config_manager import ConfigManager + from password_manager.vault import Vault + + cfg_mgr = ConfigManager( + Vault(self.encryption_manager, self.fingerprint_dir), + self.fingerprint_dir, + ) + cfg = cfg_mgr.load_config(require_pin=False) + retries = int(cfg.get("nostr_max_retries", MAX_RETRIES)) + delay = float(cfg.get("nostr_retry_delay", RETRY_DELAY)) + self.connect() self.last_error = None attempt = 0 diff --git a/src/password_manager/config_manager.py b/src/password_manager/config_manager.py index e3c9d10..08c5988 100644 --- a/src/password_manager/config_manager.py +++ b/src/password_manager/config_manager.py @@ -52,6 +52,8 @@ class ConfigManager: "secret_mode_enabled": False, "clipboard_clear_delay": 45, "quick_unlock": False, + "nostr_max_retries": 2, + "nostr_retry_delay": 1.0, "min_uppercase": 2, "min_lowercase": 2, "min_digits": 2, @@ -74,6 +76,8 @@ class ConfigManager: data.setdefault("secret_mode_enabled", False) data.setdefault("clipboard_clear_delay", 45) data.setdefault("quick_unlock", False) + data.setdefault("nostr_max_retries", 2) + data.setdefault("nostr_retry_delay", 1.0) data.setdefault("min_uppercase", 2) data.setdefault("min_lowercase", 2) data.setdefault("min_digits", 2) @@ -285,3 +289,29 @@ class ConfigManager: """Retrieve whether quick unlock is enabled.""" cfg = self.load_config(require_pin=False) return bool(cfg.get("quick_unlock", False)) + + def set_nostr_max_retries(self, retries: int) -> None: + """Persist the maximum number of Nostr retry attempts.""" + if retries < 0: + raise ValueError("retries cannot be negative") + cfg = self.load_config(require_pin=False) + cfg["nostr_max_retries"] = int(retries) + self.save_config(cfg) + + def get_nostr_max_retries(self) -> int: + """Retrieve the configured Nostr retry count.""" + cfg = self.load_config(require_pin=False) + return int(cfg.get("nostr_max_retries", 2)) + + def set_nostr_retry_delay(self, delay: float) -> None: + """Persist the delay between Nostr retry attempts.""" + if delay < 0: + raise ValueError("delay cannot be negative") + cfg = self.load_config(require_pin=False) + cfg["nostr_retry_delay"] = float(delay) + self.save_config(cfg) + + def get_nostr_retry_delay(self) -> float: + """Retrieve the delay in seconds between Nostr retries.""" + cfg = self.load_config(require_pin=False) + return float(cfg.get("nostr_retry_delay", 1.0)) diff --git a/src/seedpass/cli.py b/src/seedpass/cli.py index 6280749..1eb32d5 100644 --- a/src/seedpass/cli.py +++ b/src/seedpass/cli.py @@ -464,6 +464,8 @@ def config_set(ctx: typer.Context, key: str, value: str) -> None: "kdf_iterations": lambda v: cfg.set_kdf_iterations(int(v)), "kdf_mode": lambda v: cfg.set_kdf_mode(v), "backup_interval": lambda v: cfg.set_backup_interval(float(v)), + "nostr_max_retries": lambda v: cfg.set_nostr_max_retries(int(v)), + "nostr_retry_delay": lambda v: cfg.set_nostr_retry_delay(float(v)), "min_uppercase": lambda v: cfg.set_min_uppercase(int(v)), "min_lowercase": lambda v: cfg.set_min_lowercase(int(v)), "min_digits": lambda v: cfg.set_min_digits(int(v)), diff --git a/src/tests/test_cli_config_set_extra.py b/src/tests/test_cli_config_set_extra.py index be4cae6..6c06b0c 100644 --- a/src/tests/test_cli_config_set_extra.py +++ b/src/tests/test_cli_config_set_extra.py @@ -18,6 +18,8 @@ runner = CliRunner() ("kdf_iterations", "123", "set_kdf_iterations", 123), ("kdf_mode", "argon2", "set_kdf_mode", "argon2"), ("quick_unlock", "true", "set_quick_unlock", True), + ("nostr_max_retries", "3", "set_nostr_max_retries", 3), + ("nostr_retry_delay", "1.5", "set_nostr_retry_delay", 1.5), ( "relays", "wss://a.com, wss://b.com", diff --git a/src/tests/test_cli_doc_examples.py b/src/tests/test_cli_doc_examples.py index e9012d4..44bf430 100644 --- a/src/tests/test_cli_doc_examples.py +++ b/src/tests/test_cli_doc_examples.py @@ -63,6 +63,8 @@ class DummyPM: set_clipboard_clear_delay=lambda v: None, set_additional_backup_path=lambda v: None, set_relays=lambda v, require_pin=False: None, + set_nostr_max_retries=lambda v: None, + set_nostr_retry_delay=lambda v: None, set_offline_mode=lambda v: None, get_secret_mode_enabled=lambda: True, get_clipboard_clear_delay=lambda: 30, diff --git a/src/tests/test_config_manager.py b/src/tests/test_config_manager.py index 61016a3..d26e465 100644 --- a/src/tests/test_config_manager.py +++ b/src/tests/test_config_manager.py @@ -181,3 +181,18 @@ def test_quick_unlock_round_trip(): cfg_mgr.set_quick_unlock(True) assert cfg_mgr.get_quick_unlock() is True + + +def test_nostr_retry_settings_round_trip(): + with TemporaryDirectory() as tmpdir: + vault, _ = create_vault(Path(tmpdir), TEST_SEED, TEST_PASSWORD) + cfg_mgr = ConfigManager(vault, Path(tmpdir)) + + cfg = cfg_mgr.load_config(require_pin=False) + assert cfg["nostr_max_retries"] == 2 + assert cfg["nostr_retry_delay"] == 1.0 + + cfg_mgr.set_nostr_max_retries(5) + cfg_mgr.set_nostr_retry_delay(3.5) + assert cfg_mgr.get_nostr_max_retries() == 5 + assert cfg_mgr.get_nostr_retry_delay() == 3.5