""" Authentication Pydantic Models pentru ROA2WEB Acest modul definește toate modelele de date folosite în sistemul de autentificare, incluzând request/response models și modele pentru user data. Modelele acoperă: - Login request și response - Token data și management - User information și permisiuni - Company access control """ from pydantic import BaseModel, Field, validator, EmailStr from typing import List, Optional, Dict, Any from datetime import datetime from enum import Enum class PermissionType(str, Enum): """Tipurile de permisiuni disponibile în sistem""" READ = "read" WRITE = "write" DELETE = "delete" ADMIN = "admin" REPORTS = "reports" EXPORT = "export" class TokenType(str, Enum): """Tipurile de token-uri JWT""" ACCESS = "access" REFRESH = "refresh" class LoginRequest(BaseModel): """Model pentru request-ul de login""" username: str = Field( ..., min_length=3, max_length=50, description="Numele utilizatorului", example="admin" ) password: str = Field( ..., min_length=1, description="Parola utilizatorului" ) remember_me: bool = Field( default=False, description="Dacă să păstreze utilizatorul autentificat mai mult timp" ) @validator('username') def username_alphanumeric(cls, v): """Validează că username-ul conține doar caractere permise (inclusiv spații)""" # Permitem litere, cifre, spații, _, și - allowed_chars = v.replace(' ', '').replace('_', '').replace('-', '') if not allowed_chars.isalnum(): raise ValueError('Username-ul poate conține doar litere, cifre, spații, _ și -') return v.upper() # Convertim la uppercase pentru consistență cu Oracle class TokenResponse(BaseModel): """Model pentru răspunsul de autentificare cu token-uri""" access_token: str = Field(description="JWT access token") refresh_token: Optional[str] = Field( default=None, description="JWT refresh token (opțional)" ) token_type: str = Field( default="bearer", description="Tipul token-ului (întotdeauna 'bearer')" ) expires_in: int = Field( description="Timpul de expirare al access token-ului în secunde" ) user: 'CurrentUser' = Field(description="Informațiile utilizatorului autentificat") class RefreshTokenRequest(BaseModel): """Model pentru request-ul de refresh token""" refresh_token: str = Field(description="Refresh token-ul valid") class LogoutRequest(BaseModel): """Model pentru request-ul de logout""" refresh_token: Optional[str] = Field( default=None, description="Refresh token de invalidat (opțional)" ) class CurrentUser(BaseModel): """Model pentru utilizatorul curent autentificat""" username: str = Field(description="Numele utilizatorului") user_id: Optional[int] = Field( default=None, description="ID-ul utilizatorului în baza de date" ) email: Optional[EmailStr] = Field( default=None, description="Email-ul utilizatorului" ) companies: List[str] = Field( default_factory=list, description="Lista codurilor firmelor la care utilizatorul are acces" ) permissions: List[PermissionType] = Field( default_factory=lambda: [PermissionType.READ], description="Lista permisiunilor utilizatorului" ) is_active: bool = Field( default=True, description="Dacă utilizatorul este activ" ) last_login: Optional[datetime] = Field( default=None, description="Data ultimei autentificări" ) @validator('companies') def companies_not_empty_if_active(cls, v, values): """Validează că utilizatorii activi au cel puțin o firmă""" if values.get('is_active', True) and not v: raise ValueError('Utilizatorii activi trebuie să aibă acces la cel puțin o firmă') return v class UserCompany(BaseModel): """Model pentru o firmă la care utilizatorul are acces""" code: str = Field(description="Codul firmei (schema Oracle)") name: Optional[str] = Field( default=None, description="Numele firmei (dacă este disponibil)" ) permissions: List[PermissionType] = Field( default_factory=lambda: [PermissionType.READ], description="Permisiunile utilizatorului pentru această firmă" ) is_default: bool = Field( default=False, description="Dacă aceasta este firma implicită pentru utilizator" ) class CompanyAccessRequest(BaseModel): """Model pentru verificarea accesului la o firmă""" company_code: str = Field(description="Codul firmei de verificat") required_permissions: Optional[List[PermissionType]] = Field( default=None, description="Permisiunile necesare (opțional)" ) class CompanyAccessResponse(BaseModel): """Model pentru răspunsul de verificare acces firmă""" has_access: bool = Field(description="Dacă utilizatorul are acces") company: Optional[UserCompany] = Field( default=None, description="Detaliile firmei dacă utilizatorul are acces" ) missing_permissions: Optional[List[PermissionType]] = Field( default=None, description="Permisiunile lipsă (dacă aplicabil)" ) class AuthError(BaseModel): """Model pentru erorile de autentificare""" error: str = Field(description="Tipul erorii") error_description: str = Field(description="Descrierea detaliată a erorii") error_code: Optional[str] = Field( default=None, description="Codul de eroare pentru procesare automată" ) class AuthStats(BaseModel): """Model pentru statisticile de autentificare""" total_users: int = Field(description="Numărul total de utilizatori") active_sessions: int = Field(description="Sesiuni active curente") cache_hit_ratio: float = Field( description="Rata de hit a cache-ului pentru date utilizatori" ) last_cleanup: Optional[datetime] = Field( default=None, description="Ultima curățare a cache-ului" ) class PasswordChangeRequest(BaseModel): """Model pentru schimbarea parolei (pentru viitor)""" current_password: str = Field(description="Parola curentă") new_password: str = Field( min_length=8, description="Noua parolă (minim 8 caractere)" ) confirm_password: str = Field(description="Confirmarea noii parole") @validator('confirm_password') def passwords_match(cls, v, values): """Validează că parolele coincid""" if 'new_password' in values and v != values['new_password']: raise ValueError('Parolele nu coincid') return v class SessionInfo(BaseModel): """Model pentru informațiile despre sesiune""" session_id: str = Field(description="ID-ul sesiunii") username: str = Field(description="Numele utilizatorului") created_at: datetime = Field(description="Data creării sesiunii") last_activity: datetime = Field(description="Ultima activitate") ip_address: Optional[str] = Field( default=None, description="Adresa IP a utilizatorului" ) user_agent: Optional[str] = Field( default=None, description="User agent-ul browserului" ) is_active: bool = Field( default=True, description="Dacă sesiunea este încă activă" ) # Update la forward references pentru TokenResponse TokenResponse.model_rebuild()