import re from pydantic import BaseModel, EmailStr, field_validator from app.core.sanitize import normalize_email, sanitize_str # Common words that must not appear as whole words inside a password. # Checked case-insensitively with word boundaries. _FORBIDDEN_WORDS = { "password", "passwort", "secret", "welcome", "admin", "administrator", "login", "user", "test", "guest", "master", "dragon", "monkey", "shadow", "sunshine", "princess", "letmein", "football", "baseball", "soccer", "hockey", "abc", "qwerty", "keyboard", "computer", "internet", "access", "hello", "summer", "winter", "spring", "autumn", "flower", "mustang", "batman", "superman", "donald", "michael", "jessica", "charlie", } def _validate_password(v: str) -> str: errors = [] if len(v) < 8: errors.append("at least 8 characters") if not re.search(r"[A-Z]", v): errors.append("at least one uppercase letter") if not re.search(r"[a-z]", v): errors.append("at least one lowercase letter") if not re.search(r"\d", v): errors.append("at least one digit") if not re.search(r'[!@#$%^&*()\-_=+\[\]{};:\'",.<>?/\\|`~]', v): errors.append("at least one special character") lower = v.lower() for word in _FORBIDDEN_WORDS: # Match the word as a standalone token (surrounded by non-alpha or string boundary) if re.search(rf"(? str: return normalize_email(v) @field_validator("full_name", mode="before") @classmethod def sanitize_full_name(cls, v: str | None) -> str | None: return sanitize_str(v, max_len=128) @field_validator("password") @classmethod def password_strength(cls, v: str) -> str: return _validate_password(v) class UserOut(BaseModel): id: str email: str full_name: str | None is_active: bool model_config = {"from_attributes": True} class Token(BaseModel): access_token: str token_type: str = "bearer"