212 lines
6.5 KiB
Python
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]
|