Files
syndarix/backend/app/schemas/users.py
Felipe Cardoso e25b010b57 Include user information and token expiration in authentication responses
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.
2025-11-02 04:36:29 +01:00

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"
}
}
}