Add configurable Nostr retry settings

This commit is contained in:
thePR0M3TH3AN
2025-07-13 15:42:24 -04:00
parent 357e9f28bd
commit 78499b267e
9 changed files with 74 additions and 6 deletions

View File

@@ -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.

View File

@@ -172,8 +172,8 @@ Code: 123456
### `config` Commands
- **`seedpass config get <key>`** 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 <key> <value>`** 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 <key>`** 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 <key> <value>`** 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 scriptfriendly and can be piped into other commands.

View File

@@ -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
# -----------------------------------

View File

@@ -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

View File

@@ -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))

View File

@@ -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)),

View File

@@ -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",

View File

@@ -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,

View File

@@ -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