From d8a7cc89b6de8869419001e0dd698ca52d897db3 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sun, 30 Nov 2025 06:53:51 -0500 Subject: [PATCH] Fix gevent fork error and improve OTP email handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- app.py | 3 ++- auth.py | 51 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/app.py b/app.py index daf6147..96d2a1e 100644 --- a/app.py +++ b/app.py @@ -2162,4 +2162,5 @@ if __name__ == "__main__": app.config["LOCAL_ACTIVITIES"] = args.local_activities # Run the SocketIO server with the specified port - socketio.run(app, host="0.0.0.0", port=args.port, use_reloader=True) + # Disable reloader to avoid gevent fork compatibility issues + socketio.run(app, host="0.0.0.0", port=args.port, use_reloader=False) diff --git a/auth.py b/auth.py index 81a6207..dd0d730 100644 --- a/auth.py +++ b/auth.py @@ -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):