my-recipes/backend/email_utils.py

212 lines
6.5 KiB
Python

import os
import random
import aiosmtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime, timedelta
from dotenv import load_dotenv
load_dotenv()
# In-memory storage for verification codes (in production, use Redis or database)
verification_codes = {}
password_reset_tokens = {}
def generate_verification_code():
"""Generate a 6-digit verification code"""
return str(random.randint(100000, 999999))
async def send_verification_email(email: str, code: str, purpose: str = "password_change"):
"""Send verification code via email"""
smtp_host = os.getenv("SMTP_HOST", "smtp.gmail.com")
smtp_port = int(os.getenv("SMTP_PORT", "587"))
smtp_user = os.getenv("SMTP_USER")
smtp_password = os.getenv("SMTP_PASSWORD")
smtp_from = os.getenv("SMTP_FROM", smtp_user)
if not smtp_user or not smtp_password:
raise Exception("SMTP credentials not configured")
# Create message
message = MIMEMultipart("alternative")
message["Subject"] = "קוד אימות - מתכונים שלי"
message["From"] = smtp_from
message["To"] = email
# Email content
if purpose == "password_change":
text = f"""
שלום,
קוד האימות שלך לשינוי סיסמה הוא: {code}
הקוד תקף ל-10 דקות.
אם לא ביקשת לשנות את הסיסמה, התעלם מהודעה זו.
בברכה,
צוות מתכונים שלי
"""
html = f"""
<html dir="rtl">
<body style="font-family: Arial, sans-serif; direction: rtl; text-align: right;">
<h2>שינוי סיסמה</h2>
<p>קוד האימות שלך הוא:</p>
<h1 style="color: #22c55e; font-size: 32px; letter-spacing: 5px;">{code}</h1>
<p>הקוד תקף ל-<strong>10 דקות</strong>.</p>
<hr style="border: 1px solid #e5e7eb; margin: 20px 0;">
<p style="color: #6b7280; font-size: 14px;">
אם לא ביקשת לשנות את הסיסמה, התעלם מהודעה זו.
</p>
</body>
</html>
"""
part1 = MIMEText(text, "plain")
part2 = MIMEText(html, "html")
message.attach(part1)
message.attach(part2)
# Send email
await aiosmtplib.send(
message,
hostname=smtp_host,
port=smtp_port,
username=smtp_user,
password=smtp_password,
start_tls=True,
)
def store_verification_code(user_id: int, code: str):
"""Store verification code with expiry"""
expiry = datetime.now() + timedelta(minutes=10)
verification_codes[user_id] = {
"code": code,
"expiry": expiry
}
def verify_code(user_id: int, code: str) -> bool:
"""Verify if code is correct and not expired"""
if user_id not in verification_codes:
return False
stored = verification_codes[user_id]
# Check if expired
if datetime.now() > stored["expiry"]:
del verification_codes[user_id]
return False
# Check if code matches
if stored["code"] != code:
return False
# Code is valid, remove it
del verification_codes[user_id]
return True
async def send_password_reset_email(email: str, token: str, frontend_url: str):
"""Send password reset link via email"""
smtp_host = os.getenv("SMTP_HOST", "smtp.gmail.com")
smtp_port = int(os.getenv("SMTP_PORT", "587"))
smtp_user = os.getenv("SMTP_USER")
smtp_password = os.getenv("SMTP_PASSWORD")
smtp_from = os.getenv("SMTP_FROM", smtp_user)
if not smtp_user or not smtp_password:
raise Exception("SMTP credentials not configured")
reset_link = f"{frontend_url}?reset_token={token}"
# Create message
message = MIMEMultipart("alternative")
message["Subject"] = "איפוס סיסמה - מתכונים שלי"
message["From"] = smtp_from
message["To"] = email
text = f"""
שלום,
קיבלנו בקשה לאיפוס הסיסמה שלך.
לחץ על הקישור הבא כדי לאפס את הסיסמה (תקף ל-30 דקות):
{reset_link}
אם לא ביקשת לאפס את הסיסמה, התעלם מהודעה זו.
בברכה,
צוות מתכונים שלי
"""
html = f"""
<html dir="rtl">
<body style="font-family: Arial, sans-serif; direction: rtl; text-align: right;">
<h2>איפוס סיסמה</h2>
<p>קיבלנו בקשה לאיפוס הסיסמה שלך.</p>
<p>לחץ על הכפתור למטה כדי לאפס את הסיסמה:</p>
<div style="margin: 30px 0; text-align: center;">
<a href="{reset_link}"
style="background-color: #22c55e; color: white; padding: 12px 30px;
text-decoration: none; border-radius: 6px; display: inline-block;
font-weight: bold;">
איפוס סיסמה
</a>
</div>
<p style="color: #6b7280; font-size: 14px;">
הקישור תקף ל-<strong>30 דקות</strong>.
</p>
<hr style="border: 1px solid #e5e7eb; margin: 20px 0;">
<p style="color: #6b7280; font-size: 14px;">
אם לא ביקשת לאפס את הסיסמה, התעלם מהודעה זו.
</p>
</body>
</html>
"""
part1 = MIMEText(text, "plain")
part2 = MIMEText(html, "html")
message.attach(part1)
message.attach(part2)
# Send email
await aiosmtplib.send(
message,
hostname=smtp_host,
port=smtp_port,
username=smtp_user,
password=smtp_password,
start_tls=True,
)
def store_password_reset_token(email: str, token: str):
"""Store password reset token with expiry"""
expiry = datetime.now() + timedelta(minutes=30)
password_reset_tokens[token] = {
"email": email,
"expiry": expiry
}
def verify_reset_token(token: str) -> str:
"""Verify reset token and return email if valid"""
if token not in password_reset_tokens:
return None
stored = password_reset_tokens[token]
# Check if expired
if datetime.now() > stored["expiry"]:
del password_reset_tokens[token]
return None
return stored["email"]
def consume_reset_token(token: str):
"""Remove token after use"""
if token in password_reset_tokens:
del password_reset_tokens[token]