Updated the `Token` schema to include `user` data and `expires_in` field. Adjusted backend `auth_service.py` to populate these fields while generating tokens. Replaced `getCurrentUserInfo` with `getCurrentUserProfile` in the frontend and disabled ESLint for generated files to suppress warnings.
174 lines
4.9 KiB
Python
Executable File
174 lines
4.9 KiB
Python
Executable File
# app/schemas/users.py
|
|
from datetime import datetime
|
|
from typing import Optional, Dict, Any
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, EmailStr, field_validator, ConfigDict, Field
|
|
|
|
from app.schemas.validators import validate_password_strength, validate_phone_number
|
|
|
|
|
|
class UserBase(BaseModel):
|
|
email: EmailStr
|
|
first_name: str
|
|
last_name: Optional[str] = None
|
|
phone_number: Optional[str] = None
|
|
|
|
@field_validator('phone_number')
|
|
@classmethod
|
|
def validate_phone(cls, v: Optional[str]) -> Optional[str]:
|
|
return validate_phone_number(v)
|
|
|
|
|
|
class UserCreate(UserBase):
|
|
password: str
|
|
is_superuser: bool = False
|
|
|
|
@field_validator('password')
|
|
@classmethod
|
|
def password_strength(cls, v: str) -> str:
|
|
"""Enterprise-grade password strength validation"""
|
|
return validate_password_strength(v)
|
|
|
|
|
|
class UserUpdate(BaseModel):
|
|
first_name: Optional[str] = None
|
|
last_name: Optional[str] = None
|
|
phone_number: Optional[str] = None
|
|
password: Optional[str] = None
|
|
preferences: Optional[Dict[str, Any]] = None
|
|
is_active: Optional[bool] = None # Changed default from True to None to avoid unintended updates
|
|
is_superuser: Optional[bool] = None # Explicitly reject privilege escalation attempts
|
|
|
|
@field_validator('phone_number')
|
|
@classmethod
|
|
def validate_phone(cls, v: Optional[str]) -> Optional[str]:
|
|
return validate_phone_number(v)
|
|
|
|
@field_validator('password')
|
|
@classmethod
|
|
def password_strength(cls, v: Optional[str]) -> Optional[str]:
|
|
"""Enterprise-grade password strength validation"""
|
|
if v is None:
|
|
return v
|
|
return validate_password_strength(v)
|
|
|
|
@field_validator('is_superuser')
|
|
@classmethod
|
|
def prevent_superuser_modification(cls, v: Optional[bool]) -> Optional[bool]:
|
|
"""Prevent users from modifying their superuser status via this schema."""
|
|
if v is not None:
|
|
raise ValueError("Cannot modify superuser status through user update")
|
|
return v
|
|
|
|
|
|
class UserInDB(UserBase):
|
|
id: UUID
|
|
is_active: bool
|
|
is_superuser: bool
|
|
created_at: datetime
|
|
updated_at: Optional[datetime] = None
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class UserResponse(UserBase):
|
|
id: UUID
|
|
is_active: bool
|
|
is_superuser: bool
|
|
created_at: datetime
|
|
updated_at: Optional[datetime] = None
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class Token(BaseModel):
|
|
access_token: str
|
|
refresh_token: Optional[str] = None
|
|
token_type: str = "bearer"
|
|
user: "UserResponse" # Forward reference since UserResponse is defined above
|
|
expires_in: Optional[int] = None # Token expiration in seconds
|
|
|
|
|
|
class TokenPayload(BaseModel):
|
|
sub: str # User ID
|
|
exp: int # Expiration time
|
|
iat: Optional[int] = None # Issued at
|
|
jti: Optional[str] = None # JWT ID
|
|
is_superuser: Optional[bool] = False
|
|
first_name: Optional[str] = None
|
|
email: Optional[str] = None
|
|
type: Optional[str] = None # Token type (access/refresh)
|
|
|
|
|
|
class TokenData(BaseModel):
|
|
user_id: UUID
|
|
is_superuser: bool = False
|
|
|
|
|
|
class PasswordChange(BaseModel):
|
|
"""Schema for changing password (requires current password)."""
|
|
current_password: str
|
|
new_password: str
|
|
|
|
@field_validator('new_password')
|
|
@classmethod
|
|
def password_strength(cls, v: str) -> str:
|
|
"""Enterprise-grade password strength validation"""
|
|
return validate_password_strength(v)
|
|
|
|
|
|
class PasswordReset(BaseModel):
|
|
"""Schema for resetting password (via email token)."""
|
|
token: str
|
|
new_password: str
|
|
|
|
@field_validator('new_password')
|
|
@classmethod
|
|
def password_strength(cls, v: str) -> str:
|
|
"""Enterprise-grade password strength validation"""
|
|
return validate_password_strength(v)
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
email: EmailStr
|
|
password: str
|
|
|
|
|
|
class RefreshTokenRequest(BaseModel):
|
|
refresh_token: str
|
|
|
|
|
|
class PasswordResetRequest(BaseModel):
|
|
"""Schema for requesting a password reset."""
|
|
email: EmailStr = Field(..., description="Email address of the account")
|
|
|
|
model_config = {
|
|
"json_schema_extra": {
|
|
"example": {
|
|
"email": "user@example.com"
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class PasswordResetConfirm(BaseModel):
|
|
"""Schema for confirming a password reset with token."""
|
|
token: str = Field(..., description="Password reset token from email")
|
|
new_password: str = Field(..., min_length=8, description="New password")
|
|
|
|
@field_validator('new_password')
|
|
@classmethod
|
|
def password_strength(cls, v: str) -> str:
|
|
"""Enterprise-grade password strength validation"""
|
|
return validate_password_strength(v)
|
|
|
|
model_config = {
|
|
"json_schema_extra": {
|
|
"example": {
|
|
"token": "eyJwYXlsb2FkIjp7ImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImV4cCI6MTcxMjM0NTY3OH19",
|
|
"new_password": "NewSecurePassword123"
|
|
}
|
|
}
|
|
}
|