Add new frontend - fix 8

This commit is contained in:
Urtzi Alfaro
2025-07-22 13:46:05 +02:00
parent d04359eca5
commit 5959eb6e15
9 changed files with 873 additions and 688 deletions

View File

@@ -1,196 +1,186 @@
# ================================================================
# services/auth/app/schemas/auth.py - COMPLETE SCHEMAS
# ================================================================
# services/auth/app/schemas/auth.py - UPDATED WITH UNIFIED TOKEN RESPONSE
"""
Pydantic schemas for authentication service
Authentication schemas - Updated with unified token response format
Following industry best practices from Firebase, Cognito, etc.
"""
from pydantic import BaseModel, EmailStr, validator
from typing import Optional, List, Dict, Any
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, Dict, Any
from datetime import datetime
import re
# ================================================================
# REQUEST SCHEMAS
# ================================================================
class UserRegistration(BaseModel):
"""User registration schema"""
"""User registration request"""
email: EmailStr
password: str
full_name: str
@validator('password')
def validate_password(cls, v):
if len(v) < 8:
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')
return v
@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()
password: str = Field(..., min_length=8, max_length=128)
full_name: str = Field(..., min_length=1, max_length=255)
tenant_name: Optional[str] = Field(None, max_length=255)
class UserLogin(BaseModel):
"""User login schema"""
"""User login request"""
email: EmailStr
password: str
class RefreshTokenRequest(BaseModel):
"""Refresh token request schema"""
"""Refresh token request"""
refresh_token: str
class PasswordChange(BaseModel):
"""Password change schema"""
"""Password change request"""
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
new_password: str = Field(..., min_length=8, max_length=128)
class PasswordReset(BaseModel):
"""Password reset request schema"""
"""Password reset request"""
email: EmailStr
class PasswordResetConfirm(BaseModel):
"""Password reset confirmation schema"""
"""Password reset confirmation"""
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
new_password: str = Field(..., min_length=8, max_length=128)
# ================================================================
# RESPONSE SCHEMAS
# ================================================================
class TenantMembership(BaseModel):
"""Tenant membership information"""
tenant_id: str
tenant_name: str
role: str
is_active: bool
class TokenResponse(BaseModel):
"""Token response schema"""
access_token: str
refresh_token: str
token_type: str = "bearer"
expires_in: int = 1800 # 30 minutes
user: Dict[str, Any]
tenants: List[TenantMembership] = []
class UserResponse(BaseModel):
"""User response schema"""
class UserData(BaseModel):
"""User data embedded in token responses"""
id: str
email: str
full_name: str
is_active: bool
is_verified: bool
created_at: datetime
last_login: Optional[datetime] = None
created_at: str # ISO format datetime string
tenant_id: Optional[str] = None
role: Optional[str] = "user"
class TokenResponse(BaseModel):
"""
Unified token response for both registration and login
Follows industry standards (Firebase, AWS Cognito, etc.)
"""
access_token: str
refresh_token: Optional[str] = None
token_type: str = "bearer"
expires_in: int = 3600 # seconds
user: Optional[UserData] = None
class Config:
from_attributes = True
schema_extra = {
"example": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "def502004b8b7f8f...",
"token_type": "bearer",
"expires_in": 3600,
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"email": "user@example.com",
"full_name": "John Doe",
"is_active": True,
"is_verified": False,
"created_at": "2025-07-22T10:00:00Z",
"role": "user"
}
}
}
class UserResponse(BaseModel):
"""User response for user management endpoints"""
id: str
email: str
full_name: str
is_active: bool
is_verified: bool
created_at: str
tenant_id: Optional[str] = None
role: Optional[str] = "user"
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
exp: Optional[int] = None
message: Optional[str] = None
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] = {}
class Config:
from_attributes = True
class PasswordResetResponse(BaseModel):
"""Password reset response"""
message: str
reset_token: Optional[str] = None
# ================================================================
# UPDATE SCHEMAS
# ================================================================
class UserProfileUpdate(BaseModel):
"""User profile update schema"""
full_name: Optional[str] = None
preferences: Optional[Dict[str, Any]] = None
@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
class LogoutResponse(BaseModel):
"""Logout response"""
message: str
success: bool = True
# ================================================================
# ERROR SCHEMAS
# ================================================================
class ErrorDetail(BaseModel):
"""Error detail for API responses"""
message: str
code: Optional[str] = None
field: Optional[str] = None
class ErrorResponse(BaseModel):
"""Error response schema"""
error: str
detail: str
status_code: int
"""Standardized error response"""
success: bool = False
error: ErrorDetail
timestamp: str
class ValidationErrorResponse(BaseModel):
"""Validation error response schema"""
error: str = "validation_error"
detail: str
status_code: int = 422
errors: List[Dict[str, Any]] = []
class Config:
schema_extra = {
"example": {
"success": False,
"error": {
"message": "Invalid credentials",
"code": "AUTH_001"
},
"timestamp": "2025-07-22T10:00:00Z"
}
}
# ================================================================
# INTERNAL SCHEMAS (for service-to-service communication)
# VALIDATION SCHEMAS
# ================================================================
class UserServiceRequest(BaseModel):
"""Internal user service request"""
user_id: str
action: str
data: Optional[Dict[str, Any]] = None
class EmailVerificationRequest(BaseModel):
"""Email verification request"""
email: EmailStr
class TenantAccessRequest(BaseModel):
"""Tenant access verification request"""
user_id: str
tenant_id: str
class EmailVerificationConfirm(BaseModel):
"""Email verification confirmation"""
token: str
class TenantAccessResponse(BaseModel):
"""Tenant access verification response"""
has_access: bool
role: Optional[str] = None
tenant_name: Optional[str] = None
class ProfileUpdate(BaseModel):
"""Profile update request"""
full_name: Optional[str] = Field(None, min_length=1, max_length=255)
email: Optional[EmailStr] = None
# ================================================================
# INTERNAL SCHEMAS (for service communication)
# ================================================================
class UserContext(BaseModel):
"""User context for internal service communication"""
user_id: str
email: str
tenant_id: Optional[str] = None
roles: list[str] = ["user"]
is_verified: bool = False
class TokenClaims(BaseModel):
"""JWT token claims structure"""
sub: str # subject (user_id)
email: str
full_name: str
user_id: str
is_verified: bool
tenant_id: Optional[str] = None
iat: int # issued at
exp: int # expires at
iss: str = "bakery-auth" # issuer