From 97a0932e6715f1975d683fd9d4e369c62b072e4b Mon Sep 17 00:00:00 2001 From: dvirlabs Date: Wed, 13 May 2026 21:17:56 +0300 Subject: [PATCH] Try to fix --- backend/requirements.txt | 2 +- backend/test_ssl_connection.py | 185 +++++++++++++++++++++++++++++++++ backend/whatsapp.py | 33 +++--- 3 files changed, 206 insertions(+), 14 deletions(-) create mode 100644 backend/test_ssl_connection.py diff --git a/backend/requirements.txt b/backend/requirements.txt index 689102e..7a25397 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,7 +4,7 @@ sqlalchemy>=2.0.23 psycopg2-binary>=2.9.9 pydantic[email]>=2.5.0 httpx>=0.25.2 -certifi>=2024.0.0 +certifi>=2023.7.22 python-dotenv>=1.0.0 python-multipart>=0.0.7 openpyxl>=3.1.2 diff --git a/backend/test_ssl_connection.py b/backend/test_ssl_connection.py new file mode 100644 index 0000000..129b99a --- /dev/null +++ b/backend/test_ssl_connection.py @@ -0,0 +1,185 @@ +""" +SSL/TLS Connection Test for Meta WhatsApp API +============================================== + +This script tests SSL/TLS connectivity to Meta's API to diagnose +handshake failures. +""" + +import asyncio +import ssl +import certifi +import httpx +import sys +import os +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) + +from dotenv import load_dotenv +load_dotenv() + + +async def test_ssl_methods(): + """Test different SSL configuration methods""" + + test_url = "https://graph.facebook.com/v20.0/me" + token = os.getenv("WHATSAPP_ACCESS_TOKEN", "") + + if not token: + print("❌ WHATSAPP_ACCESS_TOKEN not set in .env") + return + + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + } + + print("="*80) + print("Testing SSL/TLS Connection to Meta's API") + print("="*80) + print(f"\nTest URL: {test_url}") + print(f"Token: ***{token[-8:]}") + print() + + # Method 1: Using certifi + print("Method 1: Using certifi CA bundle") + print("-" * 80) + try: + async with httpx.AsyncClient( + verify=certifi.where(), + timeout=10.0 + ) as client: + response = await client.get(test_url, headers=headers) + print(f"✅ SUCCESS! Status: {response.status_code}") + print(f" Response: {response.text[:200]}") + except Exception as e: + print(f"❌ FAILED: {e}") + print() + + # Method 2: Using system certificates + print("Method 2: Using system certificates (verify=True)") + print("-" * 80) + try: + async with httpx.AsyncClient( + verify=True, + timeout=10.0 + ) as client: + response = await client.get(test_url, headers=headers) + print(f"✅ SUCCESS! Status: {response.status_code}") + print(f" Response: {response.text[:200]}") + except Exception as e: + print(f"❌ FAILED: {e}") + print() + + # Method 3: Custom SSL context with TLS 1.2+ + print("Method 3: Custom SSL context (TLS 1.2+)") + print("-" * 80) + try: + ssl_context = ssl.create_default_context(cafile=certifi.where()) + ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 + ssl_context.check_hostname = True + + async with httpx.AsyncClient( + verify=ssl_context, + timeout=10.0 + ) as client: + response = await client.get(test_url, headers=headers) + print(f"✅ SUCCESS! Status: {response.status_code}") + print(f" Response: {response.text[:200]}") + except Exception as e: + print(f"❌ FAILED: {e}") + print() + + # Method 4: Custom SSL context with relaxed settings + print("Method 4: Custom SSL context (relaxed cipher suite)") + print("-" * 80) + try: + ssl_context = ssl.create_default_context(cafile=certifi.where()) + ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 + ssl_context.check_hostname = True + ssl_context.set_ciphers('DEFAULT:@SECLEVEL=1') + + async with httpx.AsyncClient( + verify=ssl_context, + timeout=10.0 + ) as client: + response = await client.get(test_url, headers=headers) + print(f"✅ SUCCESS! Status: {response.status_code}") + print(f" Response: {response.text[:200]}") + except Exception as e: + print(f"❌ FAILED: {e}") + print() + + # Method 5: No verification (INSECURE - for testing only) + print("Method 5: No SSL verification (INSECURE - testing only)") + print("-" * 80) + try: + async with httpx.AsyncClient( + verify=False, + timeout=10.0 + ) as client: + response = await client.get(test_url, headers=headers) + print(f"✅ SUCCESS! Status: {response.status_code}") + print(f" Response: {response.text[:200]}") + print(" ⚠️ WARNING: This method is INSECURE. Do not use in production!") + except Exception as e: + print(f"❌ FAILED: {e}") + print() + + # System info + print("="*80) + print("System Information") + print("="*80) + print(f"Python SSL version: {ssl.OPENSSL_VERSION}") + print(f"Certifi CA bundle location: {certifi.where()}") + print(f"httpx version: {httpx.__version__}") + + # Check if CA bundle exists + import os.path + ca_bundle = certifi.where() + if os.path.exists(ca_bundle): + file_size = os.path.getsize(ca_bundle) + print(f"✅ CA bundle exists ({file_size:,} bytes)") + else: + print(f"❌ CA bundle not found at {ca_bundle}") + + +async def main(): + print("\n" + "="*80) + print("WhatsApp Cloud API - SSL/TLS Connectivity Test") + print("="*80) + print(""" +This script tests different SSL/TLS configuration methods to identify +which one works with Meta's API on your system. + +It will try: +1. Certifi CA bundle +2. System certificates +3. Custom SSL context with TLS 1.2+ +4. Relaxed cipher suites +5. No verification (insecure, for comparison) + """) + + await test_ssl_methods() + + print("\n" + "="*80) + print("Recommendation") + print("="*80) + print(""" +Look at the results above and use the first method that succeeded. + +If only Method 5 (no verification) worked: + - Your system certificates may be outdated + - Update certifi: pip install --upgrade certifi + - Update OpenSSL on your system + +If Methods 1-4 all failed: + - Check your firewall/proxy settings + - Ensure you can reach graph.facebook.com + - Verify WHATSAPP_ACCESS_TOKEN is correct + """) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/backend/whatsapp.py b/backend/whatsapp.py index 6995600..ddceb13 100644 --- a/backend/whatsapp.py +++ b/backend/whatsapp.py @@ -17,20 +17,27 @@ logger = logging.getLogger(__name__) async def create_http_client() -> httpx.AsyncClient: """ Create an httpx client with proper certificate verification. - Uses certifi for CA bundle and explicit TLS 1.2+ negotiation. - """ - import ssl - # Create a default SSL context that prefers TLS 1.2 and higher - ssl_context = ssl.create_default_context(cafile=certifi.where()) - ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 - ssl_context.check_hostname = True + Uses certifi CA bundle for Meta's WhatsApp Cloud API. - return httpx.AsyncClient( - verify=ssl_context, - timeout=httpx.Timeout(30.0, connect=10.0), - http2=False, # Disable HTTP/2 to avoid compatibility issues - limits=httpx.Limits(max_keepalive_connections=5, max_connections=10) - ) + Falls back to system certificates if certifi fails. + """ + try: + # Try using certifi's CA bundle first (most reliable) + return httpx.AsyncClient( + verify=certifi.where(), + timeout=httpx.Timeout(30.0, connect=10.0), + http2=False, # Disable HTTP/2 for better compatibility + limits=httpx.Limits(max_keepalive_connections=5, max_connections=10) + ) + except Exception as e: + logger.warning(f"[WhatsApp] Certifi SSL setup failed, using system certs: {e}") + # Fallback: use system default certificates + return httpx.AsyncClient( + verify=True, # Use system certificates + timeout=httpx.Timeout(30.0, connect=10.0), + http2=False, + limits=httpx.Limits(max_keepalive_connections=5, max_connections=10) + )