better password validator for open search

This commit is contained in:
phact 2025-12-19 02:22:30 -05:00
parent 0bc7d57063
commit e0e9e7507d
3 changed files with 36 additions and 22 deletions

View file

@ -34,7 +34,8 @@ dependencies = [
"structlog>=25.4.0", "structlog>=25.4.0",
"docling-serve==1.5.0", "docling-serve==1.5.0",
"docling-core==2.48.1", "docling-core==2.48.1",
"easyocr>=1.7.1; sys_platform != 'darwin'" "easyocr>=1.7.1; sys_platform != 'darwin'",
"zxcvbn>=4.5.0"
] ]
[dependency-groups] [dependency-groups]

View file

@ -1,6 +1,7 @@
"""Configuration screen for OpenRAG TUI.""" """Configuration screen for OpenRAG TUI."""
import re import re
from zxcvbn import zxcvbn
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.containers import Container, Vertical, Horizontal, ScrollableContainer from textual.containers import Container, Vertical, Horizontal, ScrollableContainer
from textual.screen import Screen from textual.screen import Screen
@ -98,35 +99,36 @@ class DocumentsPathValidator(Validator):
class PasswordValidator(Validator): class PasswordValidator(Validator):
"""Validator for OpenSearch admin password.""" """Validator for OpenSearch admin password using zxcvbn strength estimation."""
# Minimum acceptable score (0-4 scale: 0=weak, 4=very strong)
MIN_SCORE = 3
def validate(self, value: str) -> ValidationResult: def validate(self, value: str) -> ValidationResult:
# Allow empty value (will be auto-generated) # Allow empty value (will be auto-generated)
if not value: if not value:
return self.success() return self.success()
# Minimum length: 8 characters # Use zxcvbn to evaluate password strength
if len(value) < 8: result = zxcvbn(value)
return self.failure("Password must be at least 8 characters long") score = result["score"]
# Check for required character types if score < self.MIN_SCORE:
has_uppercase = bool(re.search(r"[A-Z]", value)) # Get feedback from zxcvbn
has_lowercase = bool(re.search(r"[a-z]", value)) feedback = result.get("feedback", {})
has_digit = bool(re.search(r"[0-9]", value)) warning = feedback.get("warning", "")
has_special = bool(re.search(r"[!@#$%^&*()_+\-=\[\]{};':\"\\|,.<>/?]", value)) suggestions = feedback.get("suggestions", [])
missing = [] # Build error message
if not has_uppercase: strength_labels = ["very weak", "weak", "fair", "strong", "very strong"]
missing.append("uppercase letter") current_strength = strength_labels[score]
if not has_lowercase:
missing.append("lowercase letter")
if not has_digit:
missing.append("digit")
if not has_special:
missing.append("special character")
if missing: if warning:
return self.failure(f"Password must contain: {', '.join(missing)}") return self.failure(f"Password is {current_strength}: {warning}")
elif suggestions:
return self.failure(f"Password is {current_strength}. {suggestions[0]}")
else:
return self.failure(f"Password is {current_strength}. Use a longer, more unique password.")
return self.success() return self.success()
@ -200,7 +202,7 @@ class ConfigScreen(Screen):
# OpenSearch Admin Password # OpenSearch Admin Password
yield Label("OpenSearch Admin Password *") yield Label("OpenSearch Admin Password *")
yield Static( yield Static(
"Min 8 chars with uppercase, lowercase, digit, and special character", "Validate your password here: https://lowe.github.io/tryzxcvbn/",
classes="helper-text", classes="helper-text",
) )
current_value = getattr(self.env_manager.config, "opensearch_password", "") current_value = getattr(self.env_manager.config, "opensearch_password", "")

11
uv.lock generated
View file

@ -2383,6 +2383,7 @@ dependencies = [
{ name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine != 'x86_64' or sys_platform != 'linux'" }, { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine != 'x86_64' or sys_platform != 'linux'" },
{ name = "uvicorn" }, { name = "uvicorn" },
{ name = "zxcvbn" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
@ -2422,6 +2423,7 @@ requires-dist = [
{ name = "torch", marker = "platform_machine != 'x86_64' or sys_platform != 'linux'", specifier = ">=2.7.1" }, { name = "torch", marker = "platform_machine != 'x86_64' or sys_platform != 'linux'", specifier = ">=2.7.1" },
{ name = "torch", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'", specifier = ">=2.7.1", index = "https://download.pytorch.org/whl/cu128" }, { name = "torch", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'", specifier = ">=2.7.1", index = "https://download.pytorch.org/whl/cu128" },
{ name = "uvicorn", specifier = ">=0.35.0" }, { name = "uvicorn", specifier = ">=0.35.0" },
{ name = "zxcvbn", specifier = ">=4.5.0" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
@ -4173,3 +4175,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50e
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
] ]
[[package]]
name = "zxcvbn"
version = "4.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ae/40/9366940b1484fd4e9423c8decbbf34a73bf52badb36281e082fe02b57aca/zxcvbn-4.5.0.tar.gz", hash = "sha256:70392c0fff39459d7f55d0211151401e79e76fcc6e2c22b61add62900359c7c1", size = 411249, upload-time = "2025-02-19T19:03:02.699Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/16/7410f8e714a109d43d17f4e27c8eabb351557653a9b570db1bd7dfdfd822/zxcvbn-4.5.0-py2.py3-none-any.whl", hash = "sha256:2b6eed621612ce6d65e6e4c7455b966acee87d0280e257956b1f06ccc66bd5ff", size = 409397, upload-time = "2025-02-19T19:03:00.521Z" },
]