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",
"docling-serve==1.5.0",
"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]

View file

@ -1,6 +1,7 @@
"""Configuration screen for OpenRAG TUI."""
import re
from zxcvbn import zxcvbn
from textual.app import ComposeResult
from textual.containers import Container, Vertical, Horizontal, ScrollableContainer
from textual.screen import Screen
@ -98,35 +99,36 @@ class DocumentsPathValidator(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:
# Allow empty value (will be auto-generated)
if not value:
return self.success()
# Minimum length: 8 characters
if len(value) < 8:
return self.failure("Password must be at least 8 characters long")
# Use zxcvbn to evaluate password strength
result = zxcvbn(value)
score = result["score"]
# Check for required character types
has_uppercase = bool(re.search(r"[A-Z]", value))
has_lowercase = bool(re.search(r"[a-z]", value))
has_digit = bool(re.search(r"[0-9]", value))
has_special = bool(re.search(r"[!@#$%^&*()_+\-=\[\]{};':\"\\|,.<>/?]", value))
if score < self.MIN_SCORE:
# Get feedback from zxcvbn
feedback = result.get("feedback", {})
warning = feedback.get("warning", "")
suggestions = feedback.get("suggestions", [])
missing = []
if not has_uppercase:
missing.append("uppercase letter")
if not has_lowercase:
missing.append("lowercase letter")
if not has_digit:
missing.append("digit")
if not has_special:
missing.append("special character")
# Build error message
strength_labels = ["very weak", "weak", "fair", "strong", "very strong"]
current_strength = strength_labels[score]
if missing:
return self.failure(f"Password must contain: {', '.join(missing)}")
if warning:
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()
@ -200,7 +202,7 @@ class ConfigScreen(Screen):
# OpenSearch Admin Password
yield Label("OpenSearch Admin Password *")
yield Static(
"Min 8 chars with uppercase, lowercase, digit, and special character",
"Validate your password here: https://lowe.github.io/tryzxcvbn/",
classes="helper-text",
)
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.8.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine != 'x86_64' or sys_platform != 'linux'" },
{ name = "uvicorn" },
{ name = "zxcvbn" },
]
[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' and sys_platform == 'linux'", specifier = ">=2.7.1", index = "https://download.pytorch.org/whl/cu128" },
{ name = "uvicorn", specifier = ">=0.35.0" },
{ name = "zxcvbn", specifier = ">=4.5.0" },
]
[package.metadata.requires-dev]
@ -4173,3 +4175,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50e
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" },
]
[[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" },
]