"""Authentication Pydantic models for the Aniworld web application. This module defines simple request/response shapes used by the auth API and by the authentication service. Keep models small and focused so they are easy to validate and test. """ from __future__ import annotations import re from datetime import datetime, timezone from typing import Optional from pydantic import BaseModel, Field, field_validator class LoginRequest(BaseModel): """Request body for a login attempt. Fields: - password: master password string (minimum 8 chars recommended) - remember: optional flag to request a long-lived session """ password: str = Field(..., min_length=1, description="Master password") remember: Optional[bool] = Field( False, description="Keep session alive" ) class LoginResponse(BaseModel): """Response returned after a successful login.""" access_token: str = Field(..., description="JWT access token") token_type: str = Field("bearer", description="Token type") expires_at: Optional[datetime] = Field(None, description="Optional expiry timestamp") class SetupRequest(BaseModel): """Request to initialize the master password during first-time setup.""" master_password: str = Field( ..., min_length=8, description="New master password" ) anime_directory: Optional[str] = Field( None, description="Optional anime directory path" ) class AuthStatus(BaseModel): """Public status about whether auth is configured and the current user state.""" configured: bool = Field(..., description="Whether a master password is set") authenticated: bool = Field(False, description="Whether the caller is authenticated") class SessionModel(BaseModel): """Lightweight session representation stored/returned by the auth service. This model can be persisted if a persistent session store is used. """ session_id: str = Field(..., description="Unique session identifier") user: Optional[str] = Field(None, description="Username or identifier") created_at: datetime = Field( default_factory=lambda: datetime.now(timezone.utc) ) expires_at: Optional[datetime] = Field(None) class RegisterRequest(BaseModel): """Request to register a new user (for testing purposes).""" username: str = Field( ..., min_length=3, max_length=50, description="Username" ) password: str = Field(..., min_length=8, description="Password") email: str = Field(..., description="Email address") @field_validator("email") @classmethod def validate_email(cls, v: str) -> str: """Validate email format.""" # Basic email validation pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" if not re.match(pattern, v): raise ValueError("Invalid email address") return v @field_validator("username") @classmethod def validate_username(cls, v: str) -> str: """Validate username contains no special characters.""" if not re.match(r"^[a-zA-Z0-9_-]+$", v): raise ValueError( "Username can only contain letters, numbers, underscore, " "and hyphen" ) return v