first commit

This commit is contained in:
2025-11-30 00:07:24 +01:00
commit b5e642aecb
78 changed files with 15162 additions and 0 deletions

159
app/schemas/auth.py Normal file
View File

@ -0,0 +1,159 @@
"""
Authentication Schemas
Pydantic schemas for authentication and OIDC-related requests/responses.
"""
from pydantic import BaseModel, Field, field_validator
from typing import Optional, List
# ==========================================
# OIDC Authorization Schemas
# ==========================================
class AuthorizationRequest(BaseModel):
"""Schema for OIDC authorization request."""
client_id: str = Field(..., description="OAuth client ID")
redirect_uri: str = Field(..., description="Callback URL")
response_type: str = Field("code", description="Response type (only 'code' supported)")
scope: str = Field("openid", description="Requested scopes (space-separated)")
state: Optional[str] = Field(None, description="CSRF protection state")
@field_validator('response_type')
@classmethod
def validate_response_type(cls, v: str) -> str:
"""Validate that response_type is 'code'."""
if v != "code":
raise ValueError("Only 'code' response_type is supported (Authorization Code Flow)")
return v
@field_validator('scope')
@classmethod
def validate_scope(cls, v: str) -> str:
"""Validate that scope includes 'openid'."""
scopes = v.split()
if 'openid' not in scopes:
raise ValueError("Scope must include 'openid'")
return v
class AuthorizationResponse(BaseModel):
"""Schema for OIDC authorization response."""
code: str = Field(..., description="Authorization code")
state: Optional[str] = Field(None, description="State from request")
# ==========================================
# OIDC Token Schemas
# ==========================================
class TokenRequest(BaseModel):
"""Schema for OIDC token request."""
grant_type: str = Field(..., description="Grant type (authorization_code)")
code: str = Field(..., description="Authorization code")
redirect_uri: str = Field(..., description="Redirect URI (must match authorization request)")
client_id: str = Field(..., description="OAuth client ID")
client_secret: str = Field(..., description="OAuth client secret")
@field_validator('grant_type')
@classmethod
def validate_grant_type(cls, v: str) -> str:
"""Validate grant_type."""
if v != "authorization_code":
raise ValueError("Only 'authorization_code' grant_type is supported")
return v
class TokenResponse(BaseModel):
"""Schema for OIDC token response."""
access_token: str = Field(..., description="Access token")
token_type: str = Field("Bearer", description="Token type")
expires_in: int = Field(..., description="Token expiration time in seconds")
id_token: str = Field(..., description="OpenID Connect ID token")
scope: str = Field(..., description="Granted scopes")
# ==========================================
# UserInfo Schemas
# ==========================================
class UserInfoResponse(BaseModel):
"""Schema for OIDC UserInfo response."""
sub: str = Field(..., description="Subject identifier (user ID)")
username: str
email: str
name: str
preferred_username: str
role: str
permissions: List[str]
# ==========================================
# Login/Registration Schemas
# ==========================================
class LoginRequest(BaseModel):
"""Schema for user login."""
username: str = Field(..., description="Username")
password: str = Field(..., description="Password")
class LoginResponse(BaseModel):
"""Schema for login response."""
success: bool
message: str
user: Optional[dict] = None
redirect_url: Optional[str] = None
class RegisterRequest(BaseModel):
"""Schema for user registration."""
username: str = Field(..., min_length=3, max_length=80)
email: str = Field(..., description="Email address")
name: str = Field(..., min_length=1, max_length=120)
password: str = Field(..., min_length=8)
password_confirm: str = Field(..., description="Password confirmation")
preferred_username: Optional[str] = Field(None, max_length=80)
@field_validator('password')
@classmethod
def validate_password(cls, v: str) -> str:
"""Validate password strength."""
if len(v) < 8:
raise ValueError('Password must be at least 8 characters long')
return v
@field_validator('password_confirm')
@classmethod
def passwords_match(cls, v: str, info) -> str:
"""Validate that passwords match."""
if 'password' in info.data and v != info.data['password']:
raise ValueError('Passwords do not match')
return v
class RegisterResponse(BaseModel):
"""Schema for registration response."""
success: bool
message: str
user_id: Optional[int] = None
# ==========================================
# OIDC Discovery Schemas
# ==========================================
class OIDCDiscoveryResponse(BaseModel):
"""Schema for OIDC discovery document."""
issuer: str
authorization_endpoint: str
token_endpoint: str
userinfo_endpoint: str
jwks_uri: str
response_types_supported: List[str]
subject_types_supported: List[str]
id_token_signing_alg_values_supported: List[str]
scopes_supported: List[str]
token_endpoint_auth_methods_supported: List[str]
claims_supported: List[str]