Система электронной почты
Обзор
Проект включает в себя систему верификации электронной почты, которая обеспечивает безопасность и подтверждение подлинности зарегистрированных пользователей. Система использует Google SMTP для отправки электронных писем и построена на следующих принципах:
- Верификация по токену – уникальный токен генерируется при регистрации и отправляется на указанный email
- Возможность повторной отправки письма верификации
- Безопасное хранение данных пользователей
- Ограничение функциональности для неподтвержденных аккаунтов
Настройка параметров SMTP
По умолчанию проект настроен для работы с Gmail SMTP. Для настройки отправки электронной почты необходимо изменить следующие переменные окружения в файле .env
:
# SMTP настройки
SMTP_HOST=connect.smtp.bz
SMTP_PORT=587
SMTP_USER=support@devship.ru
SMTP_PASSWORD="your-password"
EMAILS_FROM_EMAIL=support@devship.ru
EMAILS_FROM_NAME="Название вашего проекта"
# Для SMTP сервиса необходимо использовать правильные учетные данные
# TLS порт: 587, SSL порт: 465
Важное примечание о Google SMTP
Для использования Gmail в качестве SMTP-сервера необходимо:
- Включить двухфакторную аутентификацию в настройках аккаунта Google
- Сгенерировать пароль приложения в настройках безопасности
- Использовать этот пароль приложения вместо обычного пароля в настройках SMTP
Основные параметры SMTP и отправки электронной почты настраиваются в файле backend/app/core/config.py
:
# Email
SMTP_TLS: bool = True
SMTP_PORT: int = 587
SMTP_HOST: str = "connect.smtp.bz"
SMTP_USER: str = os.getenv("SMTP_USER", "")
SMTP_PASSWORD: str = os.getenv("SMTP_PASSWORD", "")
EMAILS_FROM_EMAIL: Optional[EmailStr] = os.getenv("EMAILS_FROM_EMAIL")
EMAILS_FROM_NAME: Optional[str] = os.getenv("EMAILS_FROM_NAME")
EMAIL_TEMPLATES_DIR: str = "app/email-templates"
EMAILS_ENABLED: bool = False # Автоматически становится True при наличии всех необходимых настроек
Если вы хотите использовать другой SMTP-сервер, измените параметры SMTP_HOST
, SMTP_PORT
и SMTP_TLS
в соответствии с инструкциями вашего провайдера электронной почты.
Процесс верификации email
1. Регистрация пользователя
При регистрации нового пользователя происходит следующее:
- Проверка, не занят ли указанный email другим пользователем
- Создание записи пользователя в базе данных
- Генерация уникального токена верификации
- Отправка письма с ссылкой для подтверждения email
Соответствующий код находится в файле backend/app/api/routes/auth.py
в функции register
:
@router.post("/register", response_model=UserSchema, status_code=status.HTTP_201_CREATED)
async def register(
db: AsyncDB,
user_in: UserCreate,
) -> Any:
"""
Register a new user
"""
# Check if user already exists
user = await get_user_by_email(db, email=user_in.email)
if user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered",
)
# Create new user
user = await create_user(db, obj_in=user_in)
# Generate verification token
token = generate_verification_token()
await create_verification_token_for_user(db, user_id=user.id, token=token)
# Send verification email
send_verification_email(
email_to=user.email,
token=token,
username=user.full_name or user.email,
)
return user
Примечание
В текущей версии кода верификация email временно отключена (закомментирована). Для включения верификации нужно раскомментировать соответствующие строки кода в функции register
.
2. Подтверждение email
Когда пользователь переходит по ссылке из письма, происходит:
- Проверка токена на валидность и срок действия
- Маркировка токена как использованного
- Подтверждение email пользователя
Обработка подтверждения происходит в эндпоинте /api/v1/auth/verify-email
:
@router.get("/verify-email", response_model=UserSchema)
async def verify_email(
db: AsyncDB,
token: str = Query(..., description="Email verification token"),
) -> Any:
"""
Verify email address
"""
# Get verification token
verification = await get_valid_email_verification(db, token=token)
if not verification:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid or expired verification token",
)
# Mark token as used
await mark_email_verification_as_used(db, db_obj=verification)
# Mark user as verified
user = await mark_user_verified(db, user_id=verification.user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found",
)
return user
3. Повторная отправка письма верификации
Если пользователь не получил или потерял письмо с подтверждением, он может запросить повторную отправку:
@router.post("/resend-verification", response_model=UserSchema)
async def resend_verification(
db: AsyncDB,
current_user: Annotated[User, Depends(get_current_active_user)],
) -> Any:
"""
Resend verification email
"""
if current_user.is_verified:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already verified",
)
# Generate verification token
token = generate_verification_token()
await create_verification_token_for_user(db, user_id=current_user.id, token=token)
# Send verification email
send_verification_email(
email_to=current_user.email,
token=token,
username=current_user.full_name or current_user.email,
)
return current_user
Шаблоны электронных писем
Шаблоны электронных писем находятся в директории backend/app/email-templates/
. В проекте используется система шаблонов Jinja2, что позволяет создавать динамические и хорошо оформленные письма.
Основной шаблон для верификации email: email_verification.html
Для настройки внешнего вида писем вы можете отредактировать этот шаблон или создать новые в той же директории. При модификации шаблонов доступны следующие переменные:
verification_url
- URL для верификации emailusername
- имя пользователя или его emailproject_name
- название проекта
Использование этих переменных в шаблоне:
<h1>Здравствуйте, {{ username }}!</h1>
<p>Для подтверждения вашего email адреса в {{ project_name }}, пожалуйста, перейдите по ссылке:</p>
<a href="{{ verification_url }}">Подтвердить email</a>
Модель данных
Для хранения информации о верификации email используется таблица email_verifications
, определенная моделью EmailVerification
в файле backend/app/models/verification.py
:
class EmailVerification(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
token: Mapped[str] = mapped_column(String, unique=True, index=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"))
is_used: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=func.now()
)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
user: Mapped["User"] = relationship("User", back_populates="email_verifications")
Связь с пользователем определена в модели User
в файле backend/app/models/user.py
:
email_verifications: Mapped[List["EmailVerification"]] = relationship(
"EmailVerification", back_populates="user", cascade="all, delete-orphan"
)
Работа с электронной почтой в коде
Основная логика работы с электронной почтой определена в файле backend/app/utils/email.py
:
1. Генерация токена
def generate_verification_token() -> str:
"""
Generate a random token for email verification
"""
return secrets.token_urlsafe(32)
2. Отправка письма
def send_email(
email_to: str,
subject_template: str = "",
html_template: str = "",
environment: Dict[str, Any] = {},
) -> None:
"""
Send an email using the configured SMTP server
"""
assert settings.EMAILS_ENABLED, "No email settings configured"
message = emails.Message(
subject=JinjaTemplate(subject_template),
html=JinjaTemplate(html_template),
mail_from=(settings.EMAILS_FROM_NAME, settings.EMAILS_FROM_EMAIL),
)
smtp_options = {
"host": settings.SMTP_HOST,
"port": settings.SMTP_PORT,
}
if settings.SMTP_TLS:
smtp_options["tls"] = True
if settings.SMTP_USER:
smtp_options["user"] = settings.SMTP_USER
if settings.SMTP_PASSWORD:
smtp_options["password"] = settings.SMTP_PASSWORD
response = message.send(to=email_to, render=environment, smtp=smtp_options)
logging.info(f"Email sent to {email_to}, status: {response.status_code}")
3. Отправка письма верификации
def send_verification_email(email_to: str, token: str, username: str) -> None:
"""
Send an email verification email
"""
if not settings.EMAILS_ENABLED:
logging.warning("Emails not enabled, skipping verification email")
return
verification_url = f"{settings.SERVER_HOST}{settings.API_V1_STR}/auth/verify-email?token={token}"
# Load template from file
template_dir = Path(settings.EMAIL_TEMPLATES_DIR)
env = Environment(loader=FileSystemLoader(template_dir))
template = env.get_template("email_verification.html")
subject = f"{settings.PROJECT_NAME} - Email Verification"
html_content = template.render(
verification_url=verification_url,
username=username,
project_name=settings.PROJECT_NAME,
)
send_email(
email_to=email_to,
subject_template=subject,
html_template=html_content,
)
Настройка для собственного проекта
Для настройки системы электронной почты под ваш проект, выполните следующие шаги:
- Создайте или модифицируйте файл
.env
в корне проекта и добавьте переменные SMTP - При необходимости измените параметры SMTP-сервера в
backend/app/core/config.py
- Отредактируйте шаблоны писем в
backend/app/email-templates/
под свой дизайн - Убедитесь, что функция верификации email включена в коде регистрации (раскомментируйте соответствующие строки)
Советы по безопасности
- Никогда не храните пароли SMTP в исходном коде
- Используйте переменные окружения или файл
.env
для хранения чувствительной информации - Всегда используйте TLS для защиты сообщений электронной почты
- Регулярно меняйте пароли приложений для доступа к SMTP