2025-07-22 13:46:05 +02:00
|
|
|
# services/auth/app/schemas/auth.py - UPDATED WITH UNIFIED TOKEN RESPONSE
|
2025-07-17 13:09:24 +02:00
|
|
|
"""
|
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-17 13:09:24 +02:00
|
|
|
"""
|
|
|
|
|
|
2025-07-22 13:46:05 +02:00
|
|
|
from pydantic import BaseModel, EmailStr, Field
|
|
|
|
|
from typing import Optional, Dict, Any
|
2025-07-17 13:09:24 +02:00
|
|
|
from datetime import datetime
|
2025-07-20 08:33:23 +02:00
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# REQUEST SCHEMAS
|
|
|
|
|
# ================================================================
|
2025-07-17 13:09:24 +02:00
|
|
|
|
|
|
|
|
class UserRegistration(BaseModel):
|
2025-07-22 13:46:05 +02:00
|
|
|
"""User registration request"""
|
2025-07-17 13:09:24 +02:00
|
|
|
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)
|
2025-09-09 07:32:59 +02:00
|
|
|
role: Optional[str] = Field("admin", pattern=r'^(user|admin|manager|super_admin)$')
|
2025-10-01 21:56:38 +02:00
|
|
|
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")
|
2025-10-01 21:56:38 +02:00
|
|
|
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")
|
2025-07-17 13:09:24 +02:00
|
|
|
|
|
|
|
|
class UserLogin(BaseModel):
|
2025-07-22 13:46:05 +02:00
|
|
|
"""User login request"""
|
2025-07-17 13:09:24 +02:00
|
|
|
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
|
2025-09-09 07:32:59 +02:00
|
|
|
role: Optional[str] = "admin"
|
2025-07-20 08:33:23 +02:00
|
|
|
|
2025-07-17 13:09:24 +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.)
|
|
|
|
|
"""
|
2025-07-17 13:09:24 +02:00
|
|
|
access_token: str
|
2025-07-22 13:46:05 +02:00
|
|
|
refresh_token: Optional[str] = None
|
2025-07-17 13:09:24 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
2025-07-17 13:09:24 +02:00
|
|
|
|
|
|
|
|
class UserResponse(BaseModel):
|
2025-07-26 20:24:21 +02:00
|
|
|
"""User response for user management endpoints - FIXED"""
|
2025-07-17 13:09:24 +02:00
|
|
|
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
|
2025-09-09 07:32:59 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
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-17 13:09:24 +02:00
|
|
|
|
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
|
2025-09-09 07:32:59 +02:00
|
|
|
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
|