Files
guzhujushiBlog/backend/routers/password.py
T

70 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
密码重置路由。
接口(统一响应格式 {success, data, message}):
- POST /api/password/forgot 向注册邮箱发送重置验证码(未注册邮箱也返回同样提示,避免泄露)
- POST /api/password/reset 校验验证码并重置密码
"""
import os
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from ..auth import hash_password
from ..database import get_db
from ..models import PURPOSE_RESET, User
from ..schemas import (
ForgotPasswordRequest,
ResetPasswordRequest,
UnifiedResponse,
VerifyCodeRequest,
)
from ..security import is_valid_email
from .email import issue_code, verify_code
router = APIRouter(prefix="/api/password", tags=["password"])
# 密码最小长度:与注册接口保持一致,可通过 .env 的 PASSWORD_MIN_LENGTH 调整(默认 6
PASSWORD_MIN_LENGTH = int(os.getenv("PASSWORD_MIN_LENGTH") or "6")
@router.post("/forgot", response_model=UnifiedResponse)
def forgot_password(
payload: ForgotPasswordRequest,
request: Request,
db: Session = Depends(get_db),
) -> UnifiedResponse:
"""忘记密码:向已注册邮箱发送重置验证码(限流逻辑与 send-code 一致)。"""
email = payload.email.strip().lower()
if not is_valid_email(email):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="邮箱格式不正确")
user = db.query(User).filter(User.email == email).first()
if user is None:
# 未注册邮箱也返回同样提示,避免泄露邮箱是否已注册
return UnifiedResponse(success=True, data=None, message="如该邮箱已注册,验证码已发送")
issue_code(email, PURPOSE_RESET, db, request)
return UnifiedResponse(success=True, data=None, message="验证码已发送,请查收邮件")
@router.post("/reset", response_model=UnifiedResponse)
def reset_password(payload: ResetPasswordRequest, db: Session = Depends(get_db)) -> UnifiedResponse:
"""重置密码:校验验证码后更新为新密码(验证码一次性使用,且有尝试次数限制)。"""
email = payload.email.strip().lower()
if len(payload.new_password) < PASSWORD_MIN_LENGTH:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"新密码至少 {PASSWORD_MIN_LENGTH} 位")
if len(payload.new_password) > 128:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="新密码不能超过 128 位")
# 校验验证码(通过后验证码作废,一次性使用;尝试次数过多时抛 429)
verify_code(VerifyCodeRequest(email=email, code=payload.code, purpose=PURPOSE_RESET), db)
user = db.query(User).filter(User.email == email).first()
if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
user.password_hash = hash_password(payload.new_password)
# 自增令牌版本号:使该用户已签发的所有旧 JWT 立即失效(重置密码后需重新登录)
user.token_version += 1
db.commit()
return UnifiedResponse(success=True, data=None, message="密码重置成功,请使用新密码登录")