Files
oicd/app/schemas/client.py
2025-11-30 00:07:24 +01:00

94 lines
3.1 KiB
Python

"""
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