Files
bakery-ia/services/auth/app/schemas/auth.py

230 lines
8.6 KiB
Python
Raw Normal View History

2025-07-22 13:46:05 +02:00
# services/auth/app/schemas/auth.py - UPDATED WITH UNIFIED TOKEN RESPONSE
"""
2025-07-22 13:46:05 +02:00
Authentication schemas - Updated with unified token response format
Following industry best practices from Firebase, Cognito, etc.
"""
2025-07-22 13:46:05 +02:00
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, Dict, Any
from datetime import datetime
2025-07-20 08:33:23 +02:00
# ================================================================
# REQUEST SCHEMAS
# ================================================================
class UserRegistration(BaseModel):
2025-07-22 13:46:05 +02:00
"""User registration request"""
email: EmailStr
2025-07-22 13:46:05 +02:00
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)")
2026-01-13 22:22:38 +01:00
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")
2025-10-16 07:28:04 +02:00
# 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):
2025-07-22 13:46:05 +02:00
"""User login request"""
email: EmailStr
password: str
2025-07-20 08:33:23 +02:00
class RefreshTokenRequest(BaseModel):
2025-07-22 13:46:05 +02:00
"""Refresh token request"""
2025-07-20 08:33:23 +02:00
refresh_token: str
class PasswordChange(BaseModel):
2025-07-22 13:46:05 +02:00
"""Password change request"""
2025-07-20 08:33:23 +02:00
current_password: str
2025-07-22 13:46:05 +02:00
new_password: str = Field(..., min_length=8, max_length=128)
2025-07-20 08:33:23 +02:00
class PasswordReset(BaseModel):
2025-07-22 13:46:05 +02:00
"""Password reset request"""
2025-07-20 08:33:23 +02:00
email: EmailStr
class PasswordResetConfirm(BaseModel):
2025-07-22 13:46:05 +02:00
"""Password reset confirmation"""
2025-07-20 08:33:23 +02:00
token: str
2025-07-22 13:46:05 +02:00
new_password: str = Field(..., min_length=8, max_length=128)
2025-07-20 08:33:23 +02:00
# ================================================================
# RESPONSE SCHEMAS
# ================================================================
2025-07-22 13:46:05 +02:00
class UserData(BaseModel):
"""User data embedded in token responses"""
id: str
email: str
full_name: str
2025-07-20 08:33:23 +02:00
is_active: bool
2025-07-22 13:46:05 +02:00
is_verified: bool
created_at: str # ISO format datetime string
tenant_id: Optional[str] = None
role: Optional[str] = "admin"
2025-07-20 08:33:23 +02:00
class TokenResponse(BaseModel):
2025-07-22 13:46:05 +02:00
"""
Unified token response for both registration and login
Follows industry standards (Firebase, AWS Cognito, etc.)
"""
access_token: str
2025-07-22 13:46:05 +02:00
refresh_token: Optional[str] = None
token_type: str = "bearer"
2025-07-22 13:46:05 +02:00
expires_in: int = 3600 # seconds
user: Optional[UserData] = None
2026-01-13 22:22:38 +01:00
subscription_id: Optional[str] = Field(None, description="Subscription ID if created during registration")
2026-01-14 13:15:48 +01:00
# 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")
2025-07-22 13:46:05 +02:00
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"
2026-01-13 22:22:38 +01:00
},
2026-01-14 13:15:48 +01:00
"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"
2025-07-22 13:46:05 +02:00
}
}
class UserResponse(BaseModel):
2025-07-26 20:24:21 +02:00
"""User response for user management endpoints - FIXED"""
id: str
email: str
full_name: str
is_active: bool
is_verified: bool
2025-07-26 20:24:21 +02:00
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
2025-07-22 13:46:05 +02:00
tenant_id: Optional[str] = None
role: Optional[str] = "admin"
2026-01-13 22:22:38 +01:00
payment_customer_id: Optional[str] = None # ✅ Added payment integration field
default_payment_method_id: Optional[str] = None # ✅ Added payment integration field
2025-07-20 08:33:23 +02:00
2025-07-26 20:24:21 +02:00
class Config:
from_attributes = True # ✅ Enable ORM mode for SQLAlchemy objects
2025-08-08 09:08:41 +02:00
2026-01-16 15:19:34 +01:00
2025-08-08 09:08:41 +02:00
2025-07-20 08:33:23 +02:00
class TokenVerification(BaseModel):
"""Token verification response"""
valid: bool
user_id: Optional[str] = None
email: Optional[str] = None
2025-07-22 13:46:05 +02:00
exp: Optional[int] = None
message: Optional[str] = None
2025-07-22 13:46:05 +02:00
class PasswordResetResponse(BaseModel):
"""Password reset response"""
message: str
reset_token: Optional[str] = None
class LogoutResponse(BaseModel):
"""Logout response"""
message: str
success: bool = True
2025-07-19 17:49:03 +02:00
2025-07-20 08:33:23 +02:00
# ================================================================
2025-07-22 13:46:05 +02:00
# ERROR SCHEMAS
2025-07-20 08:33:23 +02:00
# ================================================================
2025-07-22 13:46:05 +02:00
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"
}
}
2025-07-20 08:33:23 +02:00
# ================================================================
2025-07-22 13:46:05 +02:00
# VALIDATION SCHEMAS
2025-07-20 08:33:23 +02:00
# ================================================================
2025-07-22 13:46:05 +02:00
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
2025-07-20 08:33:23 +02:00
# ================================================================
2025-07-22 13:46:05 +02:00
# INTERNAL SCHEMAS (for service communication)
2025-07-20 08:33:23 +02:00
# ================================================================
2025-07-22 13:46:05 +02:00
class UserContext(BaseModel):
"""User context for internal service communication"""
2025-07-19 17:49:03 +02:00
user_id: str
2025-07-22 13:46:05 +02:00
email: str
tenant_id: Optional[str] = None
roles: list[str] = ["admin"]
2025-07-22 13:46:05 +02:00
is_verified: bool = False
class TokenClaims(BaseModel):
"""JWT token claims structure"""
sub: str # subject (user_id)
email: str
full_name: str
2025-07-20 08:33:23 +02:00
user_id: str
2025-07-22 13:46:05 +02:00
is_verified: bool
tenant_id: Optional[str] = None
iat: int # issued at
exp: int # expires at
iss: str = "bakery-auth" # issuer