Files
MyAuth/src/myauth/emailing/smtp.py

212 lines
7.0 KiB
Python

"""
SMTP email service implementation.
This module provides an SMTP-based implementation of the email service
for sending authentication-related emails.
"""
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import Optional
from .base import EmailService
# Default email templates
DEFAULT_VERIFICATION_TEMPLATE = """
<html>
<body>
<h2>Email Verification</h2>
<p>Please click the link below to verify your email address:</p>
<p><a href="{verification_link}">Verify Email</a></p>
<p>Or copy and paste this link into your browser:</p>
<p>{verification_link}</p>
<p>This link will expire in 7 days.</p>
<p>If you did not request this verification, please ignore this email.</p>
</body>
</html>
"""
DEFAULT_PASSWORD_RESET_TEMPLATE = """
<html>
<body>
<h2>Password Reset</h2>
<p>You have requested to reset your password. Please click the link below:</p>
<p><a href="{reset_link}">Reset Password</a></p>
<p>Or copy and paste this link into your browser:</p>
<p>{reset_link}</p>
<p>This link will expire in 15 minutes.</p>
<p>If you did not request a password reset, please ignore this email.</p>
</body>
</html>
"""
class SMTPEmailService(EmailService):
"""
SMTP implementation of EmailService.
This implementation uses standard SMTP protocol to send emails.
It supports both TLS and SSL connections and allows custom HTML
templates for email content.
Attributes:
host: SMTP server hostname.
port: SMTP server port (587 for TLS, 465 for SSL).
username: SMTP authentication username.
password: SMTP authentication password.
use_tls: Whether to use TLS (default: True).
from_email: Email address to use as sender.
from_name: Display name for sender (optional).
base_url: Base URL for constructing verification/reset links.
verification_template: HTML template for verification emails.
password_reset_template: HTML template for password reset emails.
"""
def __init__(
self,
host: str,
port: int,
username: str,
password: str,
from_email: str,
base_url: str,
use_tls: bool = True,
from_name: Optional[str] = None,
verification_template: Optional[str] = None,
password_reset_template: Optional[str] = None
):
"""
Initialize SMTP email service.
Args:
host: SMTP server hostname (e.g., "smtp.gmail.com").
port: SMTP server port (587 for TLS, 465 for SSL).
username: SMTP authentication username.
password: SMTP authentication password.
from_email: Email address to use as sender.
base_url: Base URL for your application (e.g., "https://myapp.com").
use_tls: Whether to use TLS encryption (default: True).
from_name: Display name for sender (default: None).
verification_template: Custom HTML template for verification emails
(must include {verification_link} placeholder).
password_reset_template: Custom HTML template for reset emails
(must include {reset_link} placeholder).
Example:
>>> email_service = SMTPEmailService(
... host="smtp.gmail.com",
... port=587,
... username="noreply@myapp.com",
... password="app_password",
... from_email="noreply@myapp.com",
... base_url="https://myapp.com",
... from_name="My Application"
... )
"""
self.host = host
self.port = port
self.username = username
self.password = password
self.use_tls = use_tls
self.from_email = from_email
self.from_name = from_name
self.base_url = base_url.rstrip('/')
# Use custom templates or defaults
self.verification_template = verification_template or DEFAULT_VERIFICATION_TEMPLATE
self.password_reset_template = password_reset_template or DEFAULT_PASSWORD_RESET_TEMPLATE
def _send_email(self, to_email: str, subject: str, html_content: str) -> None:
"""
Send an email via SMTP.
Internal method that handles the actual SMTP connection and sending.
Args:
to_email: Recipient email address.
subject: Email subject line.
html_content: HTML content of the email.
Raises:
smtplib.SMTPException: If email sending fails.
"""
# Create message
message = MIMEMultipart("alternative")
message["Subject"] = subject
message["From"] = f"{self.from_name} <{self.from_email}>" if self.from_name else self.from_email
message["To"] = to_email
# Attach HTML content
html_part = MIMEText(html_content, "html")
message.attach(html_part)
# Send email
if self.use_tls:
with smtplib.SMTP(self.host, self.port) as server:
server.starttls()
server.login(self.username, self.password)
server.send_message(message)
else:
with smtplib.SMTP_SSL(self.host, self.port) as server:
server.login(self.username, self.password)
server.send_message(message)
def send_verification_email(self, email: str, token: str) -> None:
"""
Send an email verification link to the user.
Constructs a verification link using the base_url and token,
then sends an email using the configured verification template.
Args:
email: The recipient's email address.
token: The verification token (JWT).
Raises:
smtplib.SMTPException: If email sending fails.
Example:
>>> email_service.send_verification_email(
... "user@example.com",
... "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
... )
"""
verification_link = f"{self.base_url}/auth/verify-email?token={token}"
html_content = self.verification_template.format(verification_link=verification_link)
self._send_email(
to_email=email,
subject="Verify Your Email Address",
html_content=html_content
)
def send_password_reset_email(self, email: str, token: str) -> None:
"""
Send a password reset link to the user.
Constructs a password reset link using the base_url and token,
then sends an email using the configured password reset template.
Args:
email: The recipient's email address.
token: The password reset token.
Raises:
smtplib.SMTPException: If email sending fails.
Example:
>>> email_service.send_password_reset_email(
... "user@example.com",
... "a1b2c3d4e5f6..."
... )
"""
reset_link = f"{self.base_url}/reset-password?token={token}"
html_content = self.password_reset_template.format(reset_link=reset_link)
self._send_email(
to_email=email,
subject="Reset Your Password",
html_content=html_content
)