# 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 UserUpdate(BaseModel): """User update schema""" full_name: Optional[str] = None phone: Optional[str] = None language: Optional[str] = None timezone: Optional[str] = None class Config: from_attributes = True 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