Files
border-red-post-nalati/backend/app/schemas/auth.py
T

68 lines
2.0 KiB
Python

from datetime import datetime
from typing import Annotated, Literal
from pydantic import BaseModel, ConfigDict, EmailStr, Field, SecretStr, field_validator
Password = Annotated[SecretStr, Field(min_length=15, max_length=128)]
class AuthInput(BaseModel):
model_config = ConfigDict(extra="forbid")
class ProfileInput(AuthInput):
nickname: str | None = Field(None, min_length=1, max_length=80)
email: EmailStr | None = Field(None, max_length=254)
@field_validator("nickname", "email", mode="before")
@classmethod
def optional_text(cls, value):
return (value.strip() or None) if isinstance(value, str) else value
@field_validator("nickname")
@classmethod
def no_control_characters(cls, value):
if value and any(ord(char) < 32 or ord(char) == 127 for char in value):
raise ValueError("昵称不能包含控制字符")
return value
class RegisterInput(ProfileInput):
username: str = Field(min_length=3, max_length=32, pattern=r"^[a-z][a-z0-9_]{2,31}$")
password: Password
@field_validator("username", mode="before")
@classmethod
def canonical_username(cls, value):
return value.strip().lower() if isinstance(value, str) else value
class LoginInput(AuthInput):
username: str = Field(min_length=3, max_length=32, pattern=r"^[a-z][a-z0-9_]{2,31}$")
password: SecretStr = Field(min_length=1, max_length=128)
@field_validator("username", mode="before")
@classmethod
def canonical_username(cls, value):
return value.strip().lower() if isinstance(value, str) else value
class PasswordChangeInput(AuthInput):
current_password: SecretStr = Field(min_length=1, max_length=128)
new_password: Password
class UserRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
username: str
nickname: str | None
email: str | None
role: Literal["user", "admin"]
created_at: datetime
class SessionRead(UserRead):
csrf_token: str