Fix gevent fork error and improve OTP email handling

- Disable Flask reloader to prevent gevent threading conflicts
- Remove SMTP configuration guard, attempt localhost:25 first
- Add graceful fallback chain: localhost → configured SMTP → console
- Catch socket errors and continue workflow in development
- Make SMTP environment variables truly optional
This commit is contained in:
Russell Ballestrini 2025-11-30 06:53:51 -05:00
parent 41e9e8ae7c
commit d8a7cc89b6
2 changed files with 36 additions and 18 deletions

51
auth.py
View file

@ -20,7 +20,10 @@ def generate_otp():
def send_otp_email(email, otp_code):
"""Send OTP code to user's email via SMTP
Requires environment variables:
Attempts to send via localhost:25 first. If that fails, tries configured SMTP.
Falls back to console output if all methods fail.
Optional environment variables (only needed if localhost SMTP unavailable):
- SMTP_HOST: SMTP server hostname (e.g., smtp.gmail.com)
- SMTP_PORT: SMTP server port (e.g., 587)
- SMTP_USER: SMTP username/email
@ -28,19 +31,13 @@ def send_otp_email(email, otp_code):
- SMTP_FROM_EMAIL: Email address to send from
- SMTP_FROM_NAME: Display name for sender
"""
smtp_host = os.environ.get('SMTP_HOST', 'localhost')
smtp_port = int(os.environ.get('SMTP_PORT', '587'))
smtp_host = os.environ.get('SMTP_HOST')
smtp_port = int(os.environ.get('SMTP_PORT', '587')) if smtp_host else 587
smtp_user = os.environ.get('SMTP_USER')
smtp_password = os.environ.get('SMTP_PASSWORD')
from_email = os.environ.get('SMTP_FROM_EMAIL', smtp_user)
from_email = os.environ.get('SMTP_FROM_EMAIL', smtp_user or 'noreply@opencompletion.local')
from_name = os.environ.get('SMTP_FROM_NAME', 'OpenCompletion')
if not smtp_user or not smtp_password:
print("[WARNING] SMTP not configured. OTP code:", otp_code)
print(f"[WARNING] To enable email, set SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD")
# In development, still return success and print OTP
return True
# Create message
msg = MIMEMultipart('alternative')
msg['Subject'] = f'Your OpenCompletion verification code: {otp_code}'
@ -77,16 +74,36 @@ If you didn't request this code, you can safely ignore this email.
msg.attach(MIMEText(text, 'plain'))
msg.attach(MIMEText(html, 'html'))
# Try localhost:25 first (common for development with local mail server)
try:
# Send via SMTP
with smtplib.SMTP(smtp_host, smtp_port) as server:
server.starttls()
server.login(smtp_user, smtp_password)
with smtplib.SMTP('localhost', 25, timeout=2) as server:
server.send_message(msg)
print(f"[INFO] OTP sent via localhost:25 to {email}")
return True
except (ConnectionRefusedError, OSError, smtplib.SMTPException) as e:
# Localhost not available, try configured SMTP if available
if smtp_host and smtp_user and smtp_password:
try:
with smtplib.SMTP(smtp_host, smtp_port, timeout=10) as server:
server.starttls()
server.login(smtp_user, smtp_password)
server.send_message(msg)
print(f"[INFO] OTP sent via {smtp_host} to {email}")
return True
except Exception as smtp_error:
print(f"[ERROR] Failed to send OTP via {smtp_host}: {smtp_error}")
# Fall back to console output
print(f"\n{'='*60}")
print(f"[DEVELOPMENT] OTP Email - localhost:25 unavailable")
print(f"{'='*60}")
print(f"To: {email}")
print(f"Subject: Your OpenCompletion verification code: {otp_code}")
print(f"\nOTP CODE: {otp_code}")
print(f"\nThis code expires in 10 minutes.")
print(f"{'='*60}\n")
# Return True to allow development workflow
return True
except Exception as e:
print(f"[ERROR] Failed to send OTP email: {e}")
return False
def create_otp_token(email):