diff --git a/README.md b/README.md index e3d1e70..a9d620c 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ SeedPass now uses the `portalocker` library for cross-platform file locking. No - **Display TOTP Codes:** Show all active 2FA codes with a countdown timer. - **Optional External Backup Location:** Configure a second directory where backups are automatically copied. - **Auto-Lock on Inactivity:** Vault locks after a configurable timeout for additional security. +- **Quick Unlock:** Optionally skip the password prompt after verifying once. - **Secret Mode:** Copy retrieved passwords directly to your clipboard and automatically clear it after a delay. - **Tagging Support:** Organize entries with optional tags and find them quickly via search. - **Manual Vault Export/Import:** Create encrypted backups or restore them using the CLI or API. @@ -415,6 +416,7 @@ You can adjust these settings directly from the command line: ```bash seedpass config set kdf_iterations 200000 seedpass config set backup_interval 3600 +seedpass config set quick_unlock true ``` 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 61375bf..6644a02 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`, 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`, or `min_special` to adjust password complexity. +- **`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 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`, or `secret_mode_enabled` through the `config` commands. +- Adjust configuration values like `kdf_iterations`, `backup_interval`, `inactivity_timeout`, `secret_mode_enabled`, 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/docs/docs/content/index.md b/docs/docs/content/index.md index ef46fa5..2f061ed 100644 --- a/docs/docs/content/index.md +++ b/docs/docs/content/index.md @@ -51,6 +51,7 @@ SeedPass now uses the `portalocker` library for cross-platform file locking. No - **Display TOTP Codes:** Show all active 2FA codes with a countdown timer. - **Optional External Backup Location:** Configure a second directory where backups are automatically copied. - **Auto‑Lock on Inactivity:** Vault locks after a configurable timeout for additional security. +- **Quick Unlock:** Optionally skip the password prompt after verifying once. - **Secret Mode:** Copy retrieved passwords directly to your clipboard and automatically clear it after a delay. - **Tagging Support:** Organize entries with optional tags and find them quickly via search. - **Manual Vault Export/Import:** Create encrypted backups or restore them using the CLI or API. diff --git a/src/password_manager/config_manager.py b/src/password_manager/config_manager.py index c3bba67..e3c9d10 100644 --- a/src/password_manager/config_manager.py +++ b/src/password_manager/config_manager.py @@ -51,6 +51,7 @@ class ConfigManager: "backup_interval": 0, "secret_mode_enabled": False, "clipboard_clear_delay": 45, + "quick_unlock": False, "min_uppercase": 2, "min_lowercase": 2, "min_digits": 2, @@ -72,6 +73,7 @@ class ConfigManager: data.setdefault("backup_interval", 0) data.setdefault("secret_mode_enabled", False) data.setdefault("clipboard_clear_delay", 45) + data.setdefault("quick_unlock", False) data.setdefault("min_uppercase", 2) data.setdefault("min_lowercase", 2) data.setdefault("min_digits", 2) @@ -272,3 +274,14 @@ class ConfigManager: cfg = self.load_config(require_pin=False) cfg["min_special"] = int(count) self.save_config(cfg) + + def set_quick_unlock(self, enabled: bool) -> None: + """Persist the quick unlock toggle.""" + cfg = self.load_config(require_pin=False) + cfg["quick_unlock"] = bool(enabled) + self.save_config(cfg) + + def get_quick_unlock(self) -> bool: + """Retrieve whether quick unlock is enabled.""" + cfg = self.load_config(require_pin=False) + return bool(cfg.get("quick_unlock", False)) diff --git a/src/seedpass/api.py b/src/seedpass/api.py index 85a23f1..77ad26e 100644 --- a/src/seedpass/api.py +++ b/src/seedpass/api.py @@ -271,6 +271,7 @@ def update_config( "additional_backup_path": cfg.set_additional_backup_path, "secret_mode_enabled": cfg.set_secret_mode_enabled, "clipboard_clear_delay": lambda v: cfg.set_clipboard_clear_delay(int(v)), + "quick_unlock": cfg.set_quick_unlock, } action = mapping.get(key) diff --git a/src/seedpass/cli.py b/src/seedpass/cli.py index 7de30b5..6280749 100644 --- a/src/seedpass/cli.py +++ b/src/seedpass/cli.py @@ -468,6 +468,9 @@ def config_set(ctx: typer.Context, key: str, value: str) -> None: "min_lowercase": lambda v: cfg.set_min_lowercase(int(v)), "min_digits": lambda v: cfg.set_min_digits(int(v)), "min_special": lambda v: cfg.set_min_special(int(v)), + "quick_unlock": lambda v: cfg.set_quick_unlock( + v.lower() in ("1", "true", "yes", "y", "on") + ), } action = mapping.get(key) diff --git a/src/tests/test_api.py b/src/tests/test_api.py index 2e1f7e2..67f1b47 100644 --- a/src/tests/test_api.py +++ b/src/tests/test_api.py @@ -30,6 +30,7 @@ def client(monkeypatch): set_additional_backup_path=lambda v: None, set_secret_mode_enabled=lambda v: None, set_clipboard_clear_delay=lambda v: None, + set_quick_unlock=lambda v: None, ), fingerprint_manager=SimpleNamespace(list_fingerprints=lambda: ["fp"]), nostr_client=SimpleNamespace( @@ -158,6 +159,22 @@ def test_update_config(client): assert res.headers.get("access-control-allow-origin") == "http://example.com" +def test_update_config_quick_unlock(client): + cl, token = client + called = {} + + api._pm.config_manager.set_quick_unlock = lambda v: called.setdefault("val", v) + headers = {"Authorization": f"Bearer {token}", "Origin": "http://example.com"} + res = cl.put( + "/api/v1/config/quick_unlock", + json={"value": True}, + headers=headers, + ) + assert res.status_code == 200 + assert res.json() == {"status": "ok"} + assert called.get("val") is True + + def test_change_password_route(client): cl, token = client called = {} diff --git a/src/tests/test_cli_config_set_extra.py b/src/tests/test_cli_config_set_extra.py index 95abdbd..be4cae6 100644 --- a/src/tests/test_cli_config_set_extra.py +++ b/src/tests/test_cli_config_set_extra.py @@ -17,6 +17,7 @@ runner = CliRunner() ("backup_interval", "5", "set_backup_interval", 5.0), ("kdf_iterations", "123", "set_kdf_iterations", 123), ("kdf_mode", "argon2", "set_kdf_mode", "argon2"), + ("quick_unlock", "true", "set_quick_unlock", True), ( "relays", "wss://a.com, wss://b.com", diff --git a/src/tests/test_config_manager.py b/src/tests/test_config_manager.py index 9402bb8..61016a3 100644 --- a/src/tests/test_config_manager.py +++ b/src/tests/test_config_manager.py @@ -23,6 +23,7 @@ def test_config_defaults_and_round_trip(): assert cfg["pin_hash"] == "" assert cfg["password_hash"] == "" assert cfg["additional_backup_path"] == "" + assert cfg["quick_unlock"] is False assert cfg["kdf_iterations"] == 50_000 cfg_mgr.set_pin("1234") @@ -169,3 +170,14 @@ def test_backup_interval_round_trip(): cfg_mgr.set_backup_interval(15) assert cfg_mgr.get_backup_interval() == 15 + + +def test_quick_unlock_round_trip(): + with TemporaryDirectory() as tmpdir: + vault, _ = create_vault(Path(tmpdir), TEST_SEED, TEST_PASSWORD) + cfg_mgr = ConfigManager(vault, Path(tmpdir)) + + assert cfg_mgr.get_quick_unlock() is False + + cfg_mgr.set_quick_unlock(True) + assert cfg_mgr.get_quick_unlock() is True