Add password policy config options

This commit is contained in:
thePR0M3TH3AN
2025-07-30 19:10:10 -04:00
parent cfc7e455d5
commit 7dba8e138d
3 changed files with 86 additions and 0 deletions

View File

@@ -488,6 +488,16 @@ class ConfigService:
"min_lowercase": ("set_min_lowercase", int),
"min_digits": ("set_min_digits", int),
"min_special": ("set_min_special", int),
"include_special_chars": (
"set_include_special_chars",
lambda v: v.lower() in ("1", "true", "yes", "y", "on"),
),
"allowed_special_chars": ("set_allowed_special_chars", lambda v: v),
"special_mode": ("set_special_mode", lambda v: v),
"exclude_ambiguous": (
"set_exclude_ambiguous",
lambda v: v.lower() in ("1", "true", "yes", "y", "on"),
),
"quick_unlock": (
"set_quick_unlock",
lambda v: v.lower() in ("1", "true", "yes", "y", "on"),

View File

@@ -58,6 +58,10 @@ class ConfigManager:
"min_lowercase": 2,
"min_digits": 2,
"min_special": 2,
"include_special_chars": True,
"allowed_special_chars": "",
"special_mode": "standard",
"exclude_ambiguous": False,
"verbose_timing": False,
}
try:
@@ -83,6 +87,10 @@ class ConfigManager:
data.setdefault("min_lowercase", 2)
data.setdefault("min_digits", 2)
data.setdefault("min_special", 2)
data.setdefault("include_special_chars", True)
data.setdefault("allowed_special_chars", "")
data.setdefault("special_mode", "standard")
data.setdefault("exclude_ambiguous", False)
data.setdefault("verbose_timing", False)
# Migrate legacy hashed_password.enc if present and password_hash is missing
@@ -259,6 +267,10 @@ class ConfigManager:
min_lowercase=int(cfg.get("min_lowercase", 2)),
min_digits=int(cfg.get("min_digits", 2)),
min_special=int(cfg.get("min_special", 2)),
include_special_chars=bool(cfg.get("include_special_chars", True)),
allowed_special_chars=cfg.get("allowed_special_chars") or None,
special_mode=cfg.get("special_mode") or None,
exclude_ambiguous=bool(cfg.get("exclude_ambiguous", False)),
)
def set_min_uppercase(self, count: int) -> None:
@@ -281,6 +293,30 @@ class ConfigManager:
cfg["min_special"] = int(count)
self.save_config(cfg)
def set_include_special_chars(self, enabled: bool) -> None:
"""Persist whether special characters are allowed."""
cfg = self.load_config(require_pin=False)
cfg["include_special_chars"] = bool(enabled)
self.save_config(cfg)
def set_allowed_special_chars(self, chars: str | None) -> None:
"""Persist the set of allowed special characters."""
cfg = self.load_config(require_pin=False)
cfg["allowed_special_chars"] = chars or ""
self.save_config(cfg)
def set_special_mode(self, mode: str) -> None:
"""Persist the special character mode."""
cfg = self.load_config(require_pin=False)
cfg["special_mode"] = mode
self.save_config(cfg)
def set_exclude_ambiguous(self, enabled: bool) -> None:
"""Persist whether ambiguous characters are excluded."""
cfg = self.load_config(require_pin=False)
cfg["exclude_ambiguous"] = bool(enabled)
self.save_config(cfg)
def set_quick_unlock(self, enabled: bool) -> None:
"""Persist the quick unlock toggle."""
cfg = self.load_config(require_pin=False)

View File

@@ -196,3 +196,43 @@ def test_nostr_retry_settings_round_trip():
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
def test_special_char_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["include_special_chars"] is True
assert cfg["allowed_special_chars"] == ""
assert cfg["special_mode"] == "standard"
assert cfg["exclude_ambiguous"] is False
cfg_mgr.set_include_special_chars(False)
cfg_mgr.set_allowed_special_chars("@$")
cfg_mgr.set_special_mode("safe")
cfg_mgr.set_exclude_ambiguous(True)
cfg2 = cfg_mgr.load_config(require_pin=False)
assert cfg2["include_special_chars"] is False
assert cfg2["allowed_special_chars"] == "@$"
assert cfg2["special_mode"] == "safe"
assert cfg2["exclude_ambiguous"] is True
def test_password_policy_extended_fields():
with TemporaryDirectory() as tmpdir:
vault, _ = create_vault(Path(tmpdir), TEST_SEED, TEST_PASSWORD)
cfg_mgr = ConfigManager(vault, Path(tmpdir))
cfg_mgr.set_include_special_chars(False)
cfg_mgr.set_allowed_special_chars("()")
cfg_mgr.set_special_mode("safe")
cfg_mgr.set_exclude_ambiguous(True)
policy = cfg_mgr.get_password_policy()
assert policy.include_special_chars is False
assert policy.allowed_special_chars == "()"
assert policy.special_mode == "safe"
assert policy.exclude_ambiguous is True