Add new frontend - fix 8
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user