Files
2026-06-30 09:44:24 +08:00

129 lines
3.9 KiB
Python
Raw Permalink 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.
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