from __future__ import annotations from datetime import datetime from pydantic import BaseModel, EmailStr, Field, field_validator def _validate_password_strength(value: str) -> str: has_letter = any(c.isalpha() for c in value) has_digit = any(c.isdigit() for c in value) if not (has_letter and has_digit): raise ValueError("Password must contain at least one letter and one digit") return value class RegisterRequest(BaseModel): email: EmailStr password: str = Field(min_length=10, max_length=128) display_name: str = Field(min_length=1, max_length=120) timezone: str = Field(default="America/New_York", max_length=64) turnstile_token: str | None = None @field_validator("password") @classmethod def _password_strength(cls, value: str) -> str: return _validate_password_strength(value) class LoginRequest(BaseModel): email: EmailStr password: str = Field(min_length=1, max_length=128) turnstile_token: str | None = None class RefreshRequest(BaseModel): refresh_token: str class LogoutRequest(BaseModel): refresh_token: str class TokenResponse(BaseModel): access_token: str refresh_token: str token_type: str = "bearer" expires_in_minutes: int class VerifyEmailRequest(BaseModel): email: EmailStr code: str = Field(min_length=6, max_length=6) class ResendVerificationRequest(BaseModel): email: EmailStr turnstile_token: str | None = None class RequestPasswordResetRequest(BaseModel): email: EmailStr turnstile_token: str | None = None class ConfirmPasswordResetRequest(BaseModel): email: EmailStr code: str = Field(min_length=6, max_length=6) new_password: str = Field(min_length=10, max_length=128) @field_validator("new_password") @classmethod def _password_strength(cls, value: str) -> str: return _validate_password_strength(value) class DeleteAccountRequest(BaseModel): password: str = Field(min_length=1, max_length=128) class ChangePasswordRequest(BaseModel): current_password: str = Field(min_length=1, max_length=128) new_password: str = Field(min_length=10, max_length=128) @field_validator("new_password") @classmethod def _password_strength(cls, value: str) -> str: return _validate_password_strength(value) class RequirePasswordChangeRequest(BaseModel): email: EmailStr class SecurityEventResponse(BaseModel): event_type: str ip_address: str created_at: datetime model_config = {"from_attributes": True}