完善邮件验证与密码重置安全流程

This commit is contained in:
Cheng Zhou
2026-06-30 09:44:24 +08:00
parent 6c2bcc59b2
commit b25055775e
27 changed files with 4437 additions and 526 deletions
+71
View File
@@ -0,0 +1,71 @@
from __future__ import annotations
import enum
import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String, Text, UniqueConstraint, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base_class import Base
class SmtpSecurity(str, enum.Enum):
NONE = "NONE"
SSL = "SSL"
STARTTLS = "STARTTLS"
class EmailVerificationPurpose(str, enum.Enum):
REGISTER = "REGISTER"
PASSWORD_RESET = "PASSWORD_RESET"
PASSWORD_RESET_LINK = "PASSWORD_RESET_LINK"
class SystemEmailSettings(Base):
__tablename__ = "system_email_settings"
__table_args__ = (UniqueConstraint("register_domain", name="uq_system_email_settings_register_domain"),)
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
register_domain: Mapped[str] = mapped_column(String(255), nullable=False, default="huapont.cn")
smtp_host: Mapped[str] = mapped_column(String(255), nullable=False, default="")
smtp_port: Mapped[int] = mapped_column(Integer, nullable=False)
smtp_security: Mapped[SmtpSecurity] = mapped_column(
Enum(SmtpSecurity, name="smtp_security"),
nullable=False,
default=SmtpSecurity.SSL,
server_default=SmtpSecurity.SSL.value,
)
smtp_username: Mapped[str] = mapped_column(String(255), nullable=False, default="")
smtp_password_encrypted: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
sender_email: Mapped[str] = mapped_column(String(255), nullable=False, default="")
sender_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
allowed_register_domain: Mapped[str] = mapped_column(String(255), nullable=False, default="huapont.cn")
verification_code_ttl_minutes: Mapped[int] = mapped_column(Integer, nullable=False, default=10)
send_cooldown_seconds: Mapped[int] = mapped_column(Integer, nullable=False, default=60)
max_verify_attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=5)
updated_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
)
class EmailVerificationCode(Base):
__tablename__ = "email_verification_codes"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
email: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
purpose: Mapped[EmailVerificationPurpose] = mapped_column(
Enum(EmailVerificationPurpose, name="email_verification_purpose"),
nullable=False,
default=EmailVerificationPurpose.REGISTER,
server_default=EmailVerificationPurpose.REGISTER.value,
)
code_hash: Mapped[str] = mapped_column(String(255), nullable=False)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
verified_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
attempt_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())