2025-07-20 08:33:23 +02:00
|
|
|
# ================================================================
|
|
|
|
|
# services/auth/app/schemas/auth.py - COMPLETE SCHEMAS
|
|
|
|
|
# ================================================================
|
2025-07-17 13:09:24 +02:00
|
|
|
"""
|
2025-07-20 08:33:23 +02:00
|
|
|
Pydantic schemas for authentication service
|
2025-07-17 13:09:24 +02:00
|
|
|
"""
|
|
|
|
|
|
2025-07-20 08:33:23 +02:00
|
|
|
from pydantic import BaseModel, EmailStr, validator
|
|
|
|
|
from typing import Optional, List, Dict, Any
|
2025-07-17 13:09:24 +02:00
|
|
|
from datetime import datetime
|
2025-07-20 08:33:23 +02:00
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# REQUEST SCHEMAS
|
|
|
|
|
# ================================================================
|
2025-07-17 13:09:24 +02:00
|
|
|
|
|
|
|
|
class UserRegistration(BaseModel):
|
|
|
|
|
"""User registration schema"""
|
|
|
|
|
email: EmailStr
|
2025-07-20 08:33:23 +02:00
|
|
|
password: str
|
|
|
|
|
full_name: str
|
2025-07-17 13:09:24 +02:00
|
|
|
|
|
|
|
|
@validator('password')
|
|
|
|
|
def validate_password(cls, v):
|
2025-07-19 17:49:03 +02:00
|
|
|
if len(v) < 8:
|
2025-07-20 08:33:23 +02:00
|
|
|
raise ValueError('Password must be at least 8 characters long')
|
|
|
|
|
if not re.search(r'[A-Z]', v):
|
|
|
|
|
raise ValueError('Password must contain at least one uppercase letter')
|
|
|
|
|
if not re.search(r'[a-z]', v):
|
|
|
|
|
raise ValueError('Password must contain at least one lowercase letter')
|
|
|
|
|
if not re.search(r'\d', v):
|
|
|
|
|
raise ValueError('Password must contain at least one number')
|
2025-07-17 13:09:24 +02:00
|
|
|
return v
|
2025-07-20 08:33:23 +02:00
|
|
|
|
|
|
|
|
@validator('full_name')
|
|
|
|
|
def validate_full_name(cls, v):
|
|
|
|
|
if len(v.strip()) < 2:
|
|
|
|
|
raise ValueError('Full name must be at least 2 characters long')
|
|
|
|
|
return v.strip()
|
2025-07-17 13:09:24 +02:00
|
|
|
|
|
|
|
|
class UserLogin(BaseModel):
|
|
|
|
|
"""User login schema"""
|
|
|
|
|
email: EmailStr
|
|
|
|
|
password: str
|
|
|
|
|
|
2025-07-20 08:33:23 +02:00
|
|
|
class RefreshTokenRequest(BaseModel):
|
|
|
|
|
"""Refresh token request schema"""
|
|
|
|
|
refresh_token: str
|
|
|
|
|
|
|
|
|
|
class PasswordChange(BaseModel):
|
|
|
|
|
"""Password change schema"""
|
|
|
|
|
current_password: str
|
|
|
|
|
new_password: str
|
|
|
|
|
|
|
|
|
|
@validator('new_password')
|
|
|
|
|
def validate_new_password(cls, v):
|
|
|
|
|
if len(v) < 8:
|
|
|
|
|
raise ValueError('New password must be at least 8 characters long')
|
|
|
|
|
if not re.search(r'[A-Z]', v):
|
|
|
|
|
raise ValueError('New password must contain at least one uppercase letter')
|
|
|
|
|
if not re.search(r'[a-z]', v):
|
|
|
|
|
raise ValueError('New password must contain at least one lowercase letter')
|
|
|
|
|
if not re.search(r'\d', v):
|
|
|
|
|
raise ValueError('New password must contain at least one number')
|
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
class PasswordReset(BaseModel):
|
|
|
|
|
"""Password reset request schema"""
|
|
|
|
|
email: EmailStr
|
|
|
|
|
|
|
|
|
|
class PasswordResetConfirm(BaseModel):
|
|
|
|
|
"""Password reset confirmation schema"""
|
|
|
|
|
token: str
|
|
|
|
|
new_password: str
|
|
|
|
|
|
|
|
|
|
@validator('new_password')
|
|
|
|
|
def validate_new_password(cls, v):
|
|
|
|
|
if len(v) < 8:
|
|
|
|
|
raise ValueError('New password must be at least 8 characters long')
|
|
|
|
|
if not re.search(r'[A-Z]', v):
|
|
|
|
|
raise ValueError('New password must contain at least one uppercase letter')
|
|
|
|
|
if not re.search(r'[a-z]', v):
|
|
|
|
|
raise ValueError('New password must contain at least one lowercase letter')
|
|
|
|
|
if not re.search(r'\d', v):
|
|
|
|
|
raise ValueError('New password must contain at least one number')
|
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# RESPONSE SCHEMAS
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
class TenantMembership(BaseModel):
|
|
|
|
|
"""Tenant membership information"""
|
|
|
|
|
tenant_id: str
|
|
|
|
|
tenant_name: str
|
|
|
|
|
role: str
|
|
|
|
|
is_active: bool
|
|
|
|
|
|
2025-07-17 13:09:24 +02:00
|
|
|
class TokenResponse(BaseModel):
|
|
|
|
|
"""Token response schema"""
|
|
|
|
|
access_token: str
|
|
|
|
|
refresh_token: str
|
|
|
|
|
token_type: str = "bearer"
|
2025-07-20 08:33:23 +02:00
|
|
|
expires_in: int = 1800 # 30 minutes
|
|
|
|
|
user: Dict[str, Any]
|
|
|
|
|
tenants: List[TenantMembership] = []
|
2025-07-17 13:09:24 +02:00
|
|
|
|
|
|
|
|
class UserResponse(BaseModel):
|
|
|
|
|
"""User response schema"""
|
|
|
|
|
id: str
|
|
|
|
|
email: str
|
|
|
|
|
full_name: str
|
|
|
|
|
is_active: bool
|
|
|
|
|
is_verified: bool
|
2025-07-19 17:49:03 +02:00
|
|
|
created_at: datetime
|
2025-07-20 08:33:23 +02:00
|
|
|
last_login: Optional[datetime] = None
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
from_attributes = True
|
|
|
|
|
|
|
|
|
|
class TokenVerification(BaseModel):
|
|
|
|
|
"""Token verification response"""
|
|
|
|
|
valid: bool
|
|
|
|
|
user_id: Optional[str] = None
|
|
|
|
|
email: Optional[str] = None
|
|
|
|
|
full_name: Optional[str] = None
|
|
|
|
|
tenants: List[Dict[str, Any]] = []
|
|
|
|
|
expires_at: Optional[datetime] = None
|
2025-07-17 13:09:24 +02:00
|
|
|
|
2025-07-20 08:33:23 +02:00
|
|
|
class UserProfile(BaseModel):
|
|
|
|
|
"""Extended user profile"""
|
|
|
|
|
id: str
|
|
|
|
|
email: str
|
|
|
|
|
full_name: str
|
|
|
|
|
is_active: bool
|
|
|
|
|
is_verified: bool
|
|
|
|
|
created_at: datetime
|
|
|
|
|
last_login: Optional[datetime] = None
|
|
|
|
|
tenants: List[TenantMembership] = []
|
|
|
|
|
preferences: Dict[str, Any] = {}
|
|
|
|
|
|
2025-07-19 17:49:03 +02:00
|
|
|
class Config:
|
|
|
|
|
from_attributes = True
|
|
|
|
|
|
2025-07-20 08:33:23 +02:00
|
|
|
# ================================================================
|
|
|
|
|
# UPDATE SCHEMAS
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
class UserProfileUpdate(BaseModel):
|
|
|
|
|
"""User profile update schema"""
|
|
|
|
|
full_name: Optional[str] = None
|
|
|
|
|
preferences: Optional[Dict[str, Any]] = None
|
2025-07-17 13:09:24 +02:00
|
|
|
|
2025-07-20 08:33:23 +02:00
|
|
|
@validator('full_name')
|
|
|
|
|
def validate_full_name(cls, v):
|
|
|
|
|
if v is not None and len(v.strip()) < 2:
|
|
|
|
|
raise ValueError('Full name must be at least 2 characters long')
|
|
|
|
|
return v.strip() if v else v
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# ERROR SCHEMAS
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
class ErrorResponse(BaseModel):
|
|
|
|
|
"""Error response schema"""
|
|
|
|
|
error: str
|
|
|
|
|
detail: str
|
|
|
|
|
status_code: int
|
2025-07-17 13:09:24 +02:00
|
|
|
|
2025-07-20 08:33:23 +02:00
|
|
|
class ValidationErrorResponse(BaseModel):
|
|
|
|
|
"""Validation error response schema"""
|
|
|
|
|
error: str = "validation_error"
|
|
|
|
|
detail: str
|
|
|
|
|
status_code: int = 422
|
|
|
|
|
errors: List[Dict[str, Any]] = []
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# INTERNAL SCHEMAS (for service-to-service communication)
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
class UserServiceRequest(BaseModel):
|
|
|
|
|
"""Internal user service request"""
|
2025-07-19 17:49:03 +02:00
|
|
|
user_id: str
|
2025-07-20 08:33:23 +02:00
|
|
|
action: str
|
|
|
|
|
data: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
|
|
|
|
class TenantAccessRequest(BaseModel):
|
|
|
|
|
"""Tenant access verification request"""
|
|
|
|
|
user_id: str
|
|
|
|
|
tenant_id: str
|
|
|
|
|
|
|
|
|
|
class TenantAccessResponse(BaseModel):
|
|
|
|
|
"""Tenant access verification response"""
|
|
|
|
|
has_access: bool
|
|
|
|
|
role: Optional[str] = None
|
|
|
|
|
tenant_name: Optional[str] = None
|