PayrollManagementSystem/backend/app/schemas/auth.py

80 lines
2.6 KiB
Python
Raw Normal View History

"""认证相关的请求与响应模型。"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.core.constants import CAPTCHA_LENGTH, PASSWORD_MAX_BYTES
class CaptchaOut(BaseModel):
"""验证码响应。
图片以 base64 内联返回而非单独的 URL:让 id 与图片在同一响应中成对产生,
避免前端分两次请求时拿到不匹配的组合。
"""
captcha_id: str = Field(description="验证码标识,登录时需回传")
image_base64: str = Field(description="PNG 图片的 base64,不含 data: 前缀")
expires_in: int = Field(description="有效期(秒)")
class LoginIn(BaseModel):
"""登录请求。"""
model_config = ConfigDict(str_strip_whitespace=True)
username: str = Field(min_length=1, max_length=64, description="登录用户名")
password: str = Field(min_length=1, description="密码")
captcha_id: str = Field(min_length=1, max_length=64, description="验证码标识")
captcha_code: str = Field(
min_length=CAPTCHA_LENGTH,
max_length=CAPTCHA_LENGTH,
description="用户填写的验证码",
)
@field_validator("password")
@classmethod
def _check_password_bytes(cls, value: str) -> str:
"""按**字节数**校验密码长度。
必须在这里拦下:bcrypt 5.0 起对超过 72 字节的密码直接抛 ValueError,
而中文一个字符占 3 字节——按字符数校验(如 max_length=20)根本拦不住,
24 个汉字就会让哈希函数抛异常、登录接口 500。
"""
if len(value.encode("utf-8")) > PASSWORD_MAX_BYTES:
raise ValueError(f"密码超过 {PASSWORD_MAX_BYTES} 字节上限")
return value
class UserBrief(BaseModel):
"""用户简要信息。"""
model_config = ConfigDict(from_attributes=True)
id: int
username: str
real_name: str | None = None
roles: list[str] = Field(default_factory=list, description="角色编码列表")
can_write_payroll: bool = False
payroll_approval_enabled: bool = False
permissions: list[str] = Field(default_factory=list)
authorization_revision: int = 0
is_payroll_maker: bool = False
maker_required_permissions: list[str] = Field(default_factory=list)
class LoginOut(BaseModel):
"""登录成功响应。"""
access_token: str
token_type: str = "Bearer"
expires_in: int = Field(description="有效期(秒)")
user: UserBrief
class CurrentUserOut(UserBrief):
"""当前登录用户信息。"""
last_login_time: datetime | None = None