Spaces:
Sleeping
Sleeping
| """ | |
| Email Service for OTP Verification | |
| Sends OTP emails using SMTP (Gmail) | |
| """ | |
| import smtplib | |
| import random | |
| import string | |
| from email.mime.text import MIMEText | |
| from email.mime.multipart import MIMEMultipart | |
| from datetime import datetime, timedelta | |
| from typing import Optional, Tuple | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| # SMTP Configuration | |
| SMTP_SERVER = "smtp.gmail.com" | |
| SMTP_PORT = 587 | |
| SMTP_EMAIL = "jigneshprajapati.igenerate@gmail.com" | |
| SMTP_PASSWORD = "vtvgwntllsfjqykx" # NOTE: For Gmail, you MUST use an App Password! | |
| # To generate an App Password: | |
| # 1. Go to Google Account settings | |
| # 2. Security > 2-Step Verification (enable if not already) | |
| # 3. App passwords > Select app: Mail, Select device: Other (Custom name) | |
| # 4. Generate and use that 16-character password here | |
| def generate_otp(length: int = 6) -> str: | |
| """Generate a random 6-digit OTP""" | |
| return ''.join(random.choices(string.digits, k=length)) | |
| def send_otp_email(to_email: str, otp: str, purpose: str = "login") -> Tuple[bool, bool]: | |
| """ | |
| Send OTP via email with fallback support | |
| Args: | |
| to_email: Recipient email address | |
| otp: 6-digit OTP code | |
| purpose: 'login' or 'registration' | |
| Returns: | |
| Tuple[bool, bool]: (success, needs_fallback) | |
| - (True, False): Email sent successfully | |
| - (False, True): Email failed due to network issue, show OTP in UI | |
| - (False, False): Email failed due to other error | |
| """ | |
| try: | |
| # Create message | |
| msg = MIMEMultipart('alternative') | |
| msg['From'] = SMTP_EMAIL | |
| msg['To'] = to_email | |
| msg['Subject'] = f"Your OTP for {purpose.capitalize()} - Real Estate Platform" | |
| # HTML email body with AtriumChain brand theme - Clean, Professional Design | |
| html_body = f""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <style> | |
| body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Arial, sans-serif; background-color: #F5F6F8; margin: 0; padding: 0; }} | |
| .wrapper {{ max-width: 600px; margin: 40px auto; }} | |
| .container {{ background: #FFFFFF; border-radius: 24px; overflow: hidden; box-shadow: 0 4px 24px rgba(15, 16, 19, 0.08); border: 1px solid #E5E7EB; }} | |
| .header {{ background: #0F1013; padding: 48px 40px; text-align: center; }} | |
| .logo {{ font-size: 28px; font-weight: 800; color: #FFFFFF; letter-spacing: -0.5px; margin: 0; }} | |
| .logo span {{ color: #BBD2FB; }} | |
| .tagline {{ color: rgba(255,255,255,0.7); font-size: 14px; margin: 8px 0 0 0; font-weight: 400; }} | |
| .content {{ padding: 48px 40px; }} | |
| .greeting {{ color: #0F1013; font-size: 20px; font-weight: 600; margin: 0 0 16px 0; }} | |
| .message {{ color: #6B7280; font-size: 15px; line-height: 1.7; margin: 0 0 32px 0; }} | |
| .otp-container {{ background: #F5F6F8; border: 2px solid #E5E7EB; border-radius: 16px; padding: 32px; text-align: center; margin: 32px 0; }} | |
| .otp-label {{ margin: 0 0 12px 0; color: #6B7280; font-size: 12px; text-transform: uppercase; letter-spacing: 1.5px; font-weight: 600; }} | |
| .otp-code {{ font-size: 44px; font-weight: 800; color: #0F1013; letter-spacing: 8px; margin: 0; font-family: 'SF Mono', 'Roboto Mono', monospace; }} | |
| .otp-validity {{ margin: 16px 0 0 0; color: #6B7280; font-size: 13px; display: flex; align-items: center; justify-content: center; gap: 6px; }} | |
| .otp-validity svg {{ width: 14px; height: 14px; }} | |
| .security-box {{ background: #FEF3C7; border-left: 4px solid #F59E0B; border-radius: 0 12px 12px 0; padding: 20px 24px; margin: 32px 0; }} | |
| .security-title {{ color: #92400E; font-weight: 700; font-size: 14px; margin: 0 0 12px 0; display: flex; align-items: center; gap: 8px; }} | |
| .security-title svg {{ width: 16px; height: 16px; }} | |
| .security-list {{ color: #78350F; font-size: 13px; line-height: 1.8; margin: 0; padding-left: 20px; }} | |
| .security-list li {{ margin: 4px 0; }} | |
| .divider {{ height: 1px; background: #E5E7EB; margin: 32px 0; }} | |
| .footer-text {{ color: #6B7280; font-size: 14px; line-height: 1.6; margin: 0; }} | |
| .signature {{ color: #0F1013; font-weight: 600; margin: 16px 0 0 0; }} | |
| .footer {{ background: #F9FAFB; padding: 32px 40px; text-align: center; border-top: 1px solid #E5E7EB; }} | |
| .footer p {{ color: #9CA3AF; font-size: 12px; margin: 4px 0; line-height: 1.5; }} | |
| .footer-links {{ margin-top: 16px; }} | |
| .footer-link {{ color: #0F1013; text-decoration: none; font-size: 12px; font-weight: 500; }} | |
| .footer-link:hover {{ text-decoration: underline; }} | |
| .brand-badge {{ display: inline-flex; align-items: center; gap: 8px; background: #0F1013; color: #FFFFFF; padding: 8px 16px; border-radius: 100px; font-size: 12px; font-weight: 600; margin-top: 20px; }} | |
| .brand-badge svg {{ width: 16px; height: 16px; }} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrapper"> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1 class="logo">Atrium<span>Chain</span></h1> | |
| <p class="tagline">Blockchain-Powered Real Estate Investment</p> | |
| </div> | |
| <div class="content"> | |
| <p class="greeting">Hello there!</p> | |
| <p class="message">You've requested to <strong style="color: #0F1013;">{purpose}</strong> to your AtriumChain account. Use the verification code below to complete your authentication:</p> | |
| <div class="otp-container"> | |
| <p class="otp-label">Your Verification Code</p> | |
| <p class="otp-code">{otp}</p> | |
| <p class="otp-validity"> | |
| <svg fill="none" viewBox="0 0 24 24" stroke="#6B7280"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> | |
| Valid for 10 minutes | |
| </p> | |
| </div> | |
| <div class="security-box"> | |
| <div class="security-title"> | |
| <svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg> | |
| Security Notice | |
| </div> | |
| <ul class="security-list"> | |
| <li>This code expires in <strong>10 minutes</strong></li> | |
| <li>Never share this code with anyone</li> | |
| <li>AtriumChain will never ask for your OTP</li> | |
| <li>If you didn't request this, ignore this email</li> | |
| </ul> | |
| </div> | |
| <div class="divider"></div> | |
| <p class="footer-text">Need help? Contact our support team anytime. We're here to assist you with your property investment journey.</p> | |
| <p class="signature">The AtriumChain Team</p> | |
| <div style="text-align: center;"> | |
| <div class="brand-badge"> | |
| <svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg> | |
| Secured by XRP Blockchain | |
| </div> | |
| </div> | |
| </div> | |
| <div class="footer"> | |
| <p>This is an automated message from AtriumChain.</p> | |
| <p>© 2024 AtriumChain. All rights reserved.</p> | |
| <div class="footer-links"> | |
| <a href="#" class="footer-link">Privacy Policy</a> • | |
| <a href="#" class="footer-link">Terms of Service</a> • | |
| <a href="#" class="footer-link">Help Center</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| # Attach HTML body | |
| msg.attach(MIMEText(html_body, 'html')) | |
| # Connect to SMTP server and send email | |
| logger.info(f"[EMAIL] Connecting to SMTP server: {SMTP_SERVER}:{SMTP_PORT}") | |
| with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server: | |
| server.starttls() # Secure the connection | |
| logger.info(f"[EMAIL] Logging in as: {SMTP_EMAIL}") | |
| server.login(SMTP_EMAIL, SMTP_PASSWORD) | |
| logger.info(f"[EMAIL] Sending OTP to: {to_email}") | |
| server.send_message(msg) | |
| logger.info(f"[EMAIL] [SUCCESS] OTP email sent successfully to {to_email}") | |
| return True, False # Success, no fallback needed | |
| except OSError as e: | |
| # Network unreachable - common on Hugging Face where port 587 is blocked | |
| logger.error(f"[EMAIL] [ERROR] Network error (SMTP port may be blocked): {e}") | |
| logger.warning("[EMAIL] [FALLBACK] Returning OTP in API response for UI display") | |
| return False, True # Failed, but use fallback (show OTP in UI) | |
| except smtplib.SMTPAuthenticationError as e: | |
| logger.error(f"[EMAIL] [ERROR] SMTP Authentication failed: {e}") | |
| logger.error("[EMAIL] [WARNING] IMPORTANT: For Gmail, you MUST use an App Password!") | |
| logger.error("[EMAIL] Steps to generate App Password:") | |
| logger.error("[EMAIL] 1. Go to https://myaccount.google.com/security") | |
| logger.error("[EMAIL] 2. Enable 2-Step Verification if not already enabled") | |
| logger.error("[EMAIL] 3. Go to App passwords section") | |
| logger.error("[EMAIL] 4. Generate a new app password for 'Mail' application") | |
| logger.error("[EMAIL] 5. Use that 16-character password in email_service.py") | |
| return False, False # Failed, no fallback | |
| except smtplib.SMTPException as e: | |
| logger.error(f"[EMAIL] [ERROR] SMTP error: {e}") | |
| return False, False # Failed, no fallback | |
| except Exception as e: | |
| logger.error(f"[EMAIL] [ERROR] Failed to send OTP email: {e}") | |
| return False, False # Failed, no fallback | |
| def get_otp_expiry() -> datetime: | |
| """Get OTP expiry time (10 minutes from now)""" | |
| return datetime.utcnow() + timedelta(minutes=10) | |