"""Setup wizard Pydantic models. Request, response, and domain models for the first-run configuration wizard. """ from pydantic import Field, field_validator from app.models.response import BanGuiBaseModel # Top-50 most-common plaintext passwords (lower-case). # Source: aggregated public breach compilations (Have I Been Pwned, Wikipedia). # Covers passwords that pass structural checks (uppercase + digit + special char) # but are trivial to guess. _COMMON_PASSWORDS: frozenset[str] = frozenset( { "password", "password1", "password123", "password1234", "password!", "letmein", "welcome", "admin", "admin123", "administrator", "qwerty", "qwerty123", "qwerty1234", "abc123", "abcdef", "123456", "1234567", "12345678", "123456789", "1234567890", "iloveyou", "iloveyou1", "monkey", "dragon", "master", "login", "login123", "passw0rd", "passw0rd!", "changeme", "default", "guest", "guest123", "fuckyou", "fuckyou1", "shit", "asshole", "hello", "hello123", "hello!", "world", "pass", "test", "test123", "test!", "root", "root123", "p@ssword", "p@ssword1", "p@ssw0rd", "p@ssw0rd!", "sunshine", "princess", "shadow", "shadow123", "access", "access123", "mypass", "mypass123", } ) class SetupRequest(BanGuiBaseModel): """Payload for ``POST /api/setup``.""" master_password: str = Field( ..., min_length=8, max_length=72, description="Master password that protects the BanGUI interface (max 72 bytes due to bcrypt truncation).", ) @field_validator("master_password") @classmethod def validate_master_password(cls, value: str) -> str: if len(value) < 8: raise ValueError("Password must be at least 8 characters long.") if len(value) > 72: raise ValueError("Password must not exceed 72 bytes (bcrypt limitation).") if not any(char.isupper() for char in value): raise ValueError("Password must include at least one uppercase letter.") if not any(char.isdigit() for char in value): raise ValueError("Password must include at least one number.") if not any(char in "!@#$%^&*()" for char in value): raise ValueError("Password must include at least one special character (!@#$%^&*()).") if value.lower() in _COMMON_PASSWORDS: raise ValueError("Password is too common. Choose something more unique.") return value database_path: str = Field( default="bangui.db", description="Filesystem path to the BanGUI SQLite application database.", ) fail2ban_socket: str = Field( default="/var/run/fail2ban/fail2ban.sock", description="Path to the fail2ban Unix domain socket.", ) timezone: str = Field( default="UTC", description="IANA timezone name used when displaying timestamps.", ) session_duration_minutes: int = Field( default=60, ge=1, description="Number of minutes a user session remains valid.", ) class SetupResponse(BanGuiBaseModel): """Response returned after a successful initial setup.""" message: str = Field( default="Setup completed successfully. Please log in.", ) class SetupTimezoneResponse(BanGuiBaseModel): """Response for ``GET /api/setup/timezone``.""" timezone: str = Field(..., description="Configured IANA timezone identifier.") class SetupStatusResponse(BanGuiBaseModel): """Response indicating whether setup has been completed.""" completed: bool = Field( ..., description="``True`` if the initial setup has already been performed.", )