mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 15:28:44 +00:00
Merge pull request #140 from PR0M3TH3AN/codex/add-adjustable-idle-timeout-for-inactivity-lock
Add configurable inactivity timeout
This commit is contained in:
47
src/main.py
47
src/main.py
@@ -379,6 +379,40 @@ def handle_reset_relays(password_manager: PasswordManager) -> None:
|
||||
print(colored(f"Error: {e}", "red"))
|
||||
|
||||
|
||||
def handle_set_inactivity_timeout(password_manager: PasswordManager) -> None:
|
||||
"""Change the inactivity timeout for the current seed profile."""
|
||||
cfg_mgr = password_manager.config_manager
|
||||
if cfg_mgr is None:
|
||||
print(colored("Configuration manager unavailable.", "red"))
|
||||
return
|
||||
try:
|
||||
current = cfg_mgr.get_inactivity_timeout() / 60
|
||||
print(colored(f"Current timeout: {current:.1f} minutes", "cyan"))
|
||||
except Exception as e:
|
||||
logging.error(f"Error loading timeout: {e}")
|
||||
print(colored(f"Error: {e}", "red"))
|
||||
return
|
||||
value = input("Enter new timeout in minutes: ").strip()
|
||||
if not value:
|
||||
print(colored("No timeout entered.", "yellow"))
|
||||
return
|
||||
try:
|
||||
minutes = float(value)
|
||||
if minutes <= 0:
|
||||
print(colored("Timeout must be positive.", "red"))
|
||||
return
|
||||
except ValueError:
|
||||
print(colored("Invalid number.", "red"))
|
||||
return
|
||||
try:
|
||||
cfg_mgr.set_inactivity_timeout(minutes * 60)
|
||||
password_manager.inactivity_timeout = minutes * 60
|
||||
print(colored("Inactivity timeout updated.", "green"))
|
||||
except Exception as e:
|
||||
logging.error(f"Error saving timeout: {e}")
|
||||
print(colored(f"Error: {e}", "red"))
|
||||
|
||||
|
||||
def handle_profiles_menu(password_manager: PasswordManager) -> None:
|
||||
"""Submenu for managing seed profiles."""
|
||||
while True:
|
||||
@@ -461,8 +495,9 @@ def handle_settings(password_manager: PasswordManager) -> None:
|
||||
print("6. Backup Parent Seed")
|
||||
print("7. Export database")
|
||||
print("8. Import database")
|
||||
print("9. Lock Vault")
|
||||
print("10. Back")
|
||||
print("9. Set inactivity timeout")
|
||||
print("10. Lock Vault")
|
||||
print("11. Back")
|
||||
choice = input("Select an option: ").strip()
|
||||
if choice == "1":
|
||||
handle_profiles_menu(password_manager)
|
||||
@@ -488,10 +523,12 @@ def handle_settings(password_manager: PasswordManager) -> None:
|
||||
if path:
|
||||
password_manager.handle_import_database(Path(path))
|
||||
elif choice == "9":
|
||||
handle_set_inactivity_timeout(password_manager)
|
||||
elif choice == "10":
|
||||
password_manager.lock_vault()
|
||||
print(colored("Vault locked. Please re-enter your password.", "yellow"))
|
||||
password_manager.unlock_vault()
|
||||
elif choice == "10":
|
||||
elif choice == "11":
|
||||
break
|
||||
else:
|
||||
print(colored("Invalid choice.", "red"))
|
||||
@@ -651,7 +688,9 @@ if __name__ == "__main__":
|
||||
|
||||
# Display the interactive menu to the user
|
||||
try:
|
||||
display_menu(password_manager)
|
||||
display_menu(
|
||||
password_manager, inactivity_timeout=password_manager.inactivity_timeout
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Program terminated by user via KeyboardInterrupt.")
|
||||
print(colored("\nProgram terminated by user.", "yellow"))
|
||||
|
@@ -16,6 +16,7 @@ from utils.key_derivation import (
|
||||
EncryptionMode,
|
||||
DEFAULT_ENCRYPTION_MODE,
|
||||
)
|
||||
from constants import INACTIVITY_TIMEOUT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -46,6 +47,7 @@ class ConfigManager:
|
||||
"pin_hash": "",
|
||||
"password_hash": "",
|
||||
"encryption_mode": DEFAULT_ENCRYPTION_MODE.value,
|
||||
"inactivity_timeout": INACTIVITY_TIMEOUT,
|
||||
}
|
||||
try:
|
||||
data = self.vault.load_config()
|
||||
@@ -56,6 +58,7 @@ class ConfigManager:
|
||||
data.setdefault("pin_hash", "")
|
||||
data.setdefault("password_hash", "")
|
||||
data.setdefault("encryption_mode", DEFAULT_ENCRYPTION_MODE.value)
|
||||
data.setdefault("inactivity_timeout", INACTIVITY_TIMEOUT)
|
||||
|
||||
# Migrate legacy hashed_password.enc if present and password_hash is missing
|
||||
legacy_file = self.fingerprint_dir / "hashed_password.enc"
|
||||
@@ -125,3 +128,16 @@ class ConfigManager:
|
||||
config = self.load_config(require_pin=False)
|
||||
config["encryption_mode"] = mode.value
|
||||
self.save_config(config)
|
||||
|
||||
def set_inactivity_timeout(self, timeout_seconds: float) -> None:
|
||||
"""Persist the inactivity timeout in seconds."""
|
||||
if timeout_seconds <= 0:
|
||||
raise ValueError("Timeout must be positive")
|
||||
config = self.load_config(require_pin=False)
|
||||
config["inactivity_timeout"] = timeout_seconds
|
||||
self.save_config(config)
|
||||
|
||||
def get_inactivity_timeout(self) -> float:
|
||||
"""Retrieve the inactivity timeout setting in seconds."""
|
||||
config = self.load_config(require_pin=False)
|
||||
return float(config.get("inactivity_timeout", INACTIVITY_TIMEOUT))
|
||||
|
@@ -50,6 +50,7 @@ from constants import (
|
||||
MIN_PASSWORD_LENGTH,
|
||||
MAX_PASSWORD_LENGTH,
|
||||
DEFAULT_PASSWORD_LENGTH,
|
||||
INACTIVITY_TIMEOUT,
|
||||
DEFAULT_SEED_BACKUP_FILENAME,
|
||||
)
|
||||
|
||||
@@ -101,6 +102,7 @@ class PasswordManager:
|
||||
self.last_update: float = time.time()
|
||||
self.last_activity: float = time.time()
|
||||
self.locked: bool = False
|
||||
self.inactivity_timeout: float = INACTIVITY_TIMEOUT
|
||||
|
||||
# Initialize the fingerprint manager first
|
||||
self.initialize_fingerprint_manager()
|
||||
@@ -786,6 +788,9 @@ class PasswordManager:
|
||||
)
|
||||
config = self.config_manager.load_config()
|
||||
relay_list = config.get("relays", list(DEFAULT_RELAYS))
|
||||
self.inactivity_timeout = config.get(
|
||||
"inactivity_timeout", INACTIVITY_TIMEOUT
|
||||
)
|
||||
|
||||
self.nostr_client = NostrClient(
|
||||
encryption_manager=self.encryption_manager,
|
||||
|
@@ -10,6 +10,7 @@ sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||
from password_manager.config_manager import ConfigManager
|
||||
from password_manager.vault import Vault
|
||||
from nostr.client import DEFAULT_RELAYS
|
||||
from constants import INACTIVITY_TIMEOUT
|
||||
|
||||
|
||||
def test_config_defaults_and_round_trip():
|
||||
@@ -80,6 +81,19 @@ def test_set_relays_requires_at_least_one():
|
||||
cfg_mgr.set_relays([], require_pin=False)
|
||||
|
||||
|
||||
def test_inactivity_timeout_round_trip():
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
vault, enc_mgr = create_vault(Path(tmpdir), TEST_SEED, TEST_PASSWORD)
|
||||
cfg_mgr = ConfigManager(vault, Path(tmpdir))
|
||||
|
||||
cfg = cfg_mgr.load_config(require_pin=False)
|
||||
assert cfg["inactivity_timeout"] == INACTIVITY_TIMEOUT
|
||||
|
||||
cfg_mgr.set_inactivity_timeout(123)
|
||||
cfg2 = cfg_mgr.load_config(require_pin=False)
|
||||
assert cfg2["inactivity_timeout"] == 123
|
||||
|
||||
|
||||
def test_password_hash_migrates_from_file(tmp_path):
|
||||
vault, enc_mgr = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD)
|
||||
cfg_mgr = ConfigManager(vault, tmp_path)
|
||||
|
Reference in New Issue
Block a user