""" Client Schemas Pydantic schemas for OIDC client-related requests and responses. """ from pydantic import BaseModel, Field, field_validator from typing import List, Optional from datetime import datetime # ========================================== # Base Schemas # ========================================== class ClientBase(BaseModel): """Base client schema with common fields.""" client_name: str = Field(..., min_length=1, max_length=255, description="Client application name") redirect_uris: List[str] = Field(..., min_items=1, description="List of allowed redirect URIs") allowed_scopes: List[str] = Field(default_factory=lambda: ["openid", "profile", "email"], description="Allowed OAuth scopes") # ========================================== # Request Schemas (Input) # ========================================== class ClientCreate(ClientBase): """Schema for creating a new OIDC client.""" client_id: Optional[str] = Field(None, description="Client ID (auto-generated if not provided)") client_secret: Optional[str] = Field(None, description="Client secret (auto-generated if not provided)") @field_validator('redirect_uris') @classmethod def validate_redirect_uris(cls, v: List[str]) -> List[str]: """Validate redirect URIs.""" if not v: raise ValueError('At least one redirect URI is required') for uri in v: if not uri.startswith(('http://', 'https://')): raise ValueError(f'Invalid redirect URI: {uri}. Must start with http:// or https://') return v class ClientUpdate(BaseModel): """Schema for updating an OIDC client.""" client_name: Optional[str] = Field(None, min_length=1, max_length=255) redirect_uris: Optional[List[str]] = None allowed_scopes: Optional[List[str]] = None new_client_secret: Optional[str] = Field(None, description="New client secret (optional)") @field_validator('redirect_uris') @classmethod def validate_redirect_uris(cls, v: Optional[List[str]]) -> Optional[List[str]]: """Validate redirect URIs if provided.""" if v is not None: if not v: raise ValueError('At least one redirect URI is required') for uri in v: if not uri.startswith(('http://', 'https://')): raise ValueError(f'Invalid redirect URI: {uri}') return v # ========================================== # Response Schemas (Output) # ========================================== class ClientResponse(BaseModel): """Schema for client responses.""" id: int client_id: str client_name: str redirect_uris: List[str] allowed_scopes: List[str] created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class ClientWithSecret(ClientResponse): """Schema for client response including secret (only for creation).""" client_secret: str = Field(..., description="Client secret (only shown once)") class ClientListResponse(BaseModel): """Schema for client list.""" clients: List[ClientResponse] total: int