Files
bakery-ia/services/auth/app/schemas/auth.py
2026-01-16 15:19:34 +01:00

230 lines
8.6 KiB
Python

# services/auth/app/schemas/auth.py - UPDATED WITH UNIFIED TOKEN RESPONSE
"""
Authentication schemas - Updated with unified token response format
Following industry best practices from Firebase, Cognito, etc.
"""
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, Dict, Any
from datetime import datetime
# ================================================================
# REQUEST SCHEMAS
# ================================================================
class UserRegistration(BaseModel):
"""User registration request"""
email: EmailStr
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)
role: Optional[str] = Field("admin", pattern=r'^(user|admin|manager|super_admin)$')
subscription_plan: Optional[str] = Field("starter", description="Selected subscription plan (starter, professional, enterprise)")
billing_cycle: Optional[str] = Field("monthly", description="Billing cycle (monthly, yearly)")
coupon_code: Optional[str] = Field(None, description="Discount coupon code")
payment_method_id: Optional[str] = Field(None, description="Stripe payment method ID")
# GDPR Consent fields
terms_accepted: Optional[bool] = Field(True, description="Accept terms of service")
privacy_accepted: Optional[bool] = Field(True, description="Accept privacy policy")
marketing_consent: Optional[bool] = Field(False, description="Consent to marketing communications")
analytics_consent: Optional[bool] = Field(False, description="Consent to analytics cookies")
class UserLogin(BaseModel):
"""User login request"""
email: EmailStr
password: str
class RefreshTokenRequest(BaseModel):
"""Refresh token request"""
refresh_token: str
class PasswordChange(BaseModel):
"""Password change request"""
current_password: str
new_password: str = Field(..., min_length=8, max_length=128)
class PasswordReset(BaseModel):
"""Password reset request"""
email: EmailStr
class PasswordResetConfirm(BaseModel):
"""Password reset confirmation"""
token: str
new_password: str = Field(..., min_length=8, max_length=128)
# ================================================================
# RESPONSE SCHEMAS
# ================================================================
class UserData(BaseModel):
"""User data embedded in token responses"""
id: str
email: str
full_name: str
is_active: bool
is_verified: bool
created_at: str # ISO format datetime string
tenant_id: Optional[str] = None
role: Optional[str] = "admin"
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
subscription_id: Optional[str] = Field(None, description="Subscription ID if created during registration")
# Payment action fields (3DS, SetupIntent, etc.)
requires_action: Optional[bool] = Field(None, description="Whether payment action is required (3DS, SetupIntent confirmation)")
action_type: Optional[str] = Field(None, description="Type of action required (setup_intent_confirmation, payment_intent_confirmation)")
client_secret: Optional[str] = Field(None, description="Client secret for payment confirmation")
payment_intent_id: Optional[str] = Field(None, description="Payment intent ID for 3DS authentication")
setup_intent_id: Optional[str] = Field(None, description="SetupIntent ID for payment method verification")
customer_id: Optional[str] = Field(None, description="Stripe customer ID")
# Additional fields for post-confirmation subscription completion
plan_id: Optional[str] = Field(None, description="Subscription plan ID")
payment_method_id: Optional[str] = Field(None, description="Payment method ID")
trial_period_days: Optional[int] = Field(None, description="Trial period in days")
user_id: Optional[str] = Field(None, description="User ID for post-confirmation processing")
billing_interval: Optional[str] = Field(None, description="Billing interval (monthly, yearly)")
message: Optional[str] = Field(None, description="Additional message about payment action required")
class Config:
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"
},
"subscription_id": "sub_1234567890",
"requires_action": True,
"action_type": "setup_intent_confirmation",
"client_secret": "seti_1234_secret_5678",
"payment_intent_id": None,
"setup_intent_id": "seti_1234567890",
"customer_id": "cus_1234567890"
}
}
class UserResponse(BaseModel):
"""User response for user management endpoints - FIXED"""
id: str
email: str
full_name: str
is_active: bool
is_verified: bool
created_at: datetime # ✅ Changed from str to datetime
last_login: Optional[datetime] = None # ✅ Added missing field
phone: Optional[str] = None # ✅ Added missing field
language: Optional[str] = None # ✅ Added missing field
timezone: Optional[str] = None # ✅ Added missing field
tenant_id: Optional[str] = None
role: Optional[str] = "admin"
payment_customer_id: Optional[str] = None # ✅ Added payment integration field
default_payment_method_id: Optional[str] = None # ✅ Added payment integration field
class Config:
from_attributes = True # ✅ Enable ORM mode for SQLAlchemy objects
class TokenVerification(BaseModel):
"""Token verification response"""
valid: bool
user_id: Optional[str] = None
email: Optional[str] = None
exp: Optional[int] = None
message: Optional[str] = None
class PasswordResetResponse(BaseModel):
"""Password reset response"""
message: str
reset_token: Optional[str] = None
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):
"""Standardized error response"""
success: bool = False
error: ErrorDetail
timestamp: str
class Config:
schema_extra = {
"example": {
"success": False,
"error": {
"message": "Invalid credentials",
"code": "AUTH_001"
},
"timestamp": "2025-07-22T10:00:00Z"
}
}
# ================================================================
# VALIDATION SCHEMAS
# ================================================================
class EmailVerificationRequest(BaseModel):
"""Email verification request"""
email: EmailStr
class EmailVerificationConfirm(BaseModel):
"""Email verification confirmation"""
token: str
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] = ["admin"]
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