完善邮件验证与密码重置安全流程
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator
|
||||
|
||||
SmtpSecurityValue = Literal["NONE", "SSL", "STARTTLS"]
|
||||
|
||||
|
||||
class EmailSettingsRead(BaseModel):
|
||||
register_domain: str = "huapont.cn"
|
||||
smtp_host: str = ""
|
||||
smtp_port: int = 465
|
||||
smtp_security: SmtpSecurityValue = "SSL"
|
||||
smtp_username: str = ""
|
||||
smtp_password_configured: bool = False
|
||||
sender_email: str = ""
|
||||
sender_name: Optional[str] = "CTMS 系统"
|
||||
allowed_register_domain: str = "huapont.cn,qq.com"
|
||||
verification_code_ttl_minutes: int = 10
|
||||
send_cooldown_seconds: int = 60
|
||||
max_verify_attempts: int = 5
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class EmailSettingsUpdate(BaseModel):
|
||||
smtp_host: str = Field(default="", max_length=255)
|
||||
smtp_port: int = Field(ge=1, le=65535)
|
||||
smtp_security: SmtpSecurityValue = "SSL"
|
||||
smtp_username: str = Field(default="", max_length=255)
|
||||
smtp_password: Optional[str] = Field(default=None, max_length=500)
|
||||
sender_email: str = Field(default="", max_length=255)
|
||||
sender_name: Optional[str] = Field(default="CTMS 系统", max_length=255)
|
||||
allowed_register_domain: str = Field(default="", max_length=255)
|
||||
verification_code_ttl_minutes: int = Field(default=10, ge=1, le=60)
|
||||
send_cooldown_seconds: int = Field(default=60, ge=10, le=3600)
|
||||
max_verify_attempts: int = Field(default=5, ge=1, le=20)
|
||||
|
||||
@field_validator("allowed_register_domain")
|
||||
@classmethod
|
||||
def normalize_domain(cls, value: str) -> str:
|
||||
domains = []
|
||||
for item in value.replace(",", ",").split(","):
|
||||
domain = item.strip().lower().lstrip("@")
|
||||
if domain and domain not in domains:
|
||||
domains.append(domain)
|
||||
if not domains:
|
||||
raise ValueError("至少配置一个允许注册邮箱域名")
|
||||
return ",".join(domains)
|
||||
|
||||
|
||||
class EmailTestRequest(BaseModel):
|
||||
recipient_email: EmailStr
|
||||
|
||||
|
||||
class EmailDomainCreateRequest(BaseModel):
|
||||
register_domain: str = Field(min_length=1, max_length=255)
|
||||
|
||||
@field_validator("register_domain")
|
||||
@classmethod
|
||||
def normalize_domain(cls, value: str) -> str:
|
||||
return value.strip().lower().lstrip("@")
|
||||
|
||||
|
||||
class EmailSettingsListResponse(BaseModel):
|
||||
items: list[EmailSettingsRead]
|
||||
total: int
|
||||
|
||||
|
||||
class RegisterEmailCodeSendRequest(BaseModel):
|
||||
email: EmailStr
|
||||
|
||||
|
||||
class RegisterEmailCodeVerifyRequest(BaseModel):
|
||||
email: EmailStr
|
||||
code: str = Field(min_length=4, max_length=12)
|
||||
|
||||
|
||||
class PasswordResetLinkSendRequest(BaseModel):
|
||||
email: EmailStr
|
||||
|
||||
|
||||
class PasswordResetRequest(BaseModel):
|
||||
email: EmailStr
|
||||
code: str = Field(min_length=4, max_length=12)
|
||||
password: str = Field(min_length=8, max_length=72)
|
||||
|
||||
@field_validator("password")
|
||||
@classmethod
|
||||
def validate_password_strength(cls, value: str) -> str:
|
||||
has_letter = any(ch.isalpha() for ch in value)
|
||||
has_number = any(ch.isdigit() for ch in value)
|
||||
if not has_letter or not has_number:
|
||||
raise ValueError("密码需至少 8 位且包含字母和数字")
|
||||
return value
|
||||
|
||||
|
||||
class PasswordResetCodeVerifyRequest(BaseModel):
|
||||
email: EmailStr
|
||||
code: str = Field(min_length=4, max_length=12)
|
||||
|
||||
|
||||
class PasswordResetTokenRequest(BaseModel):
|
||||
token: str = Field(min_length=20, max_length=200)
|
||||
password: str = Field(min_length=8, max_length=72)
|
||||
|
||||
@field_validator("password")
|
||||
@classmethod
|
||||
def validate_password_strength(cls, value: str) -> str:
|
||||
has_letter = any(ch.isalpha() for ch in value)
|
||||
has_number = any(ch.isdigit() for ch in value)
|
||||
if not has_letter or not has_number:
|
||||
raise ValueError("密码需至少 8 位且包含字母和数字")
|
||||
return value
|
||||
|
||||
|
||||
class EmailCodeResponse(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class EmailCodeVerifyResponse(BaseModel):
|
||||
verified: bool
|
||||
|
||||
|
||||
class PasswordResetCodeVerifyResponse(BaseModel):
|
||||
verified: bool
|
||||
reset_token: str
|
||||
Reference in New Issue
Block a user