923ff63a3d
后端: - security.py: 新增 bcrypt 密码哈希/校验工具 - auth_service.py: 新增 login_with_password、reset_password_with_sms - auth.py: 新增 /login-password、/has-password、/set-password、/reset-password 接口 - schemas/auth.py: 新增 PasswordLoginRequest、SetPasswordRequest、ResetPasswordRequest、CheckPasswordResponse - crud/user.py: 新增 update_password 前端: - Login.tsx: 支持验证码/密码切换登录,密码模式下显示忘记密码入口 - Login.css: 新增登录方式切换标签、密码输入框样式 - authStore.ts: 新增 loginWithPassword - Settings.tsx: 新增账号安全区块,显示密码状态,打开设置/修改密码弹窗 - SetPasswordModal.tsx: 设置/修改密码弹窗(旧密码校验、密码显示切换、表单验证) - ResetPasswordModal.tsx: 忘记密码弹窗(手机号+验证码+新密码重置) 兼容: - 零数据库迁移,password_hash 字段已存在(nullable) - 现有接口不变,完全向后兼容旧版本
81 lines
2.9 KiB
Python
81 lines
2.9 KiB
Python
"""
|
||
认证相关 Schema
|
||
===============
|
||
"""
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
from app.schemas.user import UserInfo
|
||
|
||
|
||
class MobileLoginRequest(BaseModel):
|
||
"""手机号验证码登录请求"""
|
||
|
||
mobile: str = Field(..., description="手机号", min_length=11, max_length=20)
|
||
code: str = Field(..., description="短信验证码", min_length=4, max_length=10)
|
||
device_id: str = Field(..., description="设备唯一标识")
|
||
device_name: str | None = Field(None, description="设备名称")
|
||
os_info: str | None = Field(None, description="操作系统信息")
|
||
app_version: str | None = Field(None, description="应用版本号")
|
||
|
||
|
||
class PasswordLoginRequest(BaseModel):
|
||
"""手机号密码登录请求"""
|
||
|
||
mobile: str = Field(..., description="手机号", min_length=11, max_length=20)
|
||
password: str = Field(..., description="密码", min_length=6, max_length=128)
|
||
device_id: str = Field(..., description="设备唯一标识")
|
||
device_name: str | None = Field(None, description="设备名称")
|
||
os_info: str | None = Field(None, description="操作系统信息")
|
||
app_version: str | None = Field(None, description="应用版本号")
|
||
|
||
|
||
class SendSmsCodeRequest(BaseModel):
|
||
"""发送短信验证码请求"""
|
||
|
||
mobile: str = Field(..., description="手机号", min_length=11, max_length=20)
|
||
|
||
|
||
class SetPasswordRequest(BaseModel):
|
||
"""设置/修改密码请求"""
|
||
|
||
old_password: str | None = Field(None, description="旧密码(修改时必填)", max_length=128)
|
||
new_password: str = Field(..., description="新密码", min_length=6, max_length=128)
|
||
|
||
|
||
class ResetPasswordRequest(BaseModel):
|
||
"""短信验证码重置密码请求"""
|
||
|
||
mobile: str = Field(..., description="手机号", min_length=11, max_length=20)
|
||
code: str = Field(..., description="短信验证码", min_length=4, max_length=10)
|
||
new_password: str = Field(..., description="新密码", min_length=6, max_length=128)
|
||
|
||
|
||
class RefreshTokenRequest(BaseModel):
|
||
"""刷新 Token 请求"""
|
||
|
||
refresh_token: str = Field(..., description="Refresh Token")
|
||
|
||
|
||
class TokenResponse(BaseModel):
|
||
"""Token 响应(登录/刷新共用)"""
|
||
|
||
access_token: str = Field(..., description="Access Token(30 分钟有效)")
|
||
refresh_token: str = Field(..., description="Refresh Token(30 天有效)")
|
||
user: UserInfo = Field(..., description="用户信息")
|
||
|
||
|
||
class CheckPasswordResponse(BaseModel):
|
||
"""检查是否设置过密码响应"""
|
||
|
||
has_password: bool = Field(..., description="是否已设置密码")
|
||
|
||
|
||
class TokenPayload(BaseModel):
|
||
"""Token 载荷"""
|
||
|
||
sub: str | None = Field(None, description="用户 ID")
|
||
type: str | None = Field(None, description="Token 类型:access / refresh")
|
||
jti: str | None = Field(None, description="JWT 唯一标识(Refresh Token 用)")
|
||
exp: int | None = Field(None, description="过期时间戳")
|