diff --git a/.vscode/settings.json b/.vscode/settings.json index 133e687..35d9ae1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,9 +19,6 @@ "notebook.output.textLineLimit": 200, "jupyter.debugJustMyCode": false, "python.testing.pytestEnabled": true, - "python.testing.pytestArgs": [ - "tests" - ], "files.exclude": { "**/*.egg-info": true, "**/htmlcov": true, diff --git a/pyproject.toml b/pyproject.toml index 830503d..35b64a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ alpine-bits-server = "alpine_bits_python.main:main" packages = ["src/alpine_bits_python"] [tool.pytest.ini_options] -testpaths = ["test"] +testpaths = ["tests"] pythonpath = ["src"] [tool.ruff] diff --git a/src/alpine_bits_python/scripts/setup_security.py b/src/alpine_bits_python/scripts/setup_security.py deleted file mode 100644 index 20028bd..0000000 --- a/src/alpine_bits_python/scripts/setup_security.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 -"""Configuration and setup script for the Wix Form Handler API -""" - -import os -import secrets -import sys - -# Add parent directory to path to import from src -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -from alpine_bits_python.auth import generate_api_key - - -def generate_secure_keys(): - """Generate secure API keys for the application""" - print("šŸ” Generating Secure API Keys") - print("=" * 50) - - # Generate API keys - wix_api_key = generate_api_key() - admin_api_key = generate_api_key() - webhook_secret = secrets.token_urlsafe(32) - - print(f"šŸ”‘ Wix Webhook API Key: {wix_api_key}") - print(f"šŸ” Admin API Key: {admin_api_key}") - print(f"šŸ”’ Webhook Secret: {webhook_secret}") - - print("\nšŸ“‹ Environment Variables") - print("-" * 30) - print(f"export WIX_API_KEY='{wix_api_key}'") - print(f"export ADMIN_API_KEY='{admin_api_key}'") - print(f"export WIX_WEBHOOK_SECRET='{webhook_secret}'") - print("export REDIS_URL='redis://localhost:6379' # Optional for production") - - print("\nšŸ”§ .env File Content") - print("-" * 20) - print(f"WIX_API_KEY={wix_api_key}") - print(f"ADMIN_API_KEY={admin_api_key}") - print(f"WIX_WEBHOOK_SECRET={webhook_secret}") - print("REDIS_URL=redis://localhost:6379") - - # Optionally write to .env file - create_env = input("\nā“ Create .env file? (y/n): ").lower().strip() - if create_env == "y": - # Create .env in the project root (two levels up from scripts) - env_path = os.path.join( - os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env" - ) - with open(env_path, "w") as f: - f.write(f"WIX_API_KEY={wix_api_key}\n") - f.write(f"ADMIN_API_KEY={admin_api_key}\n") - f.write(f"WIX_WEBHOOK_SECRET={webhook_secret}\n") - f.write("REDIS_URL=redis://localhost:6379\n") - print(f"āœ… .env file created at {env_path}!") - print("āš ļø Add .env to your .gitignore file!") - - print("\n🌐 Wix Configuration") - print("-" * 20) - print("1. In your Wix site, go to Settings > Webhooks") - print("2. Add webhook URL: https://yourdomain.com/webhook/wix-form") - print("3. Add custom header: Authorization: Bearer " + wix_api_key) - print("4. Optionally configure webhook signature with the secret above") - - return { - "wix_api_key": wix_api_key, - "admin_api_key": admin_api_key, - "webhook_secret": webhook_secret, - } - - -def check_security_setup(): - """Check current security configuration""" - print("šŸ” Security Configuration Check") - print("=" * 40) - - # Check environment variables - wix_key = os.getenv("WIX_API_KEY") - admin_key = os.getenv("ADMIN_API_KEY") - webhook_secret = os.getenv("WIX_WEBHOOK_SECRET") - redis_url = os.getenv("REDIS_URL") - - print("Environment Variables:") - print(f" WIX_API_KEY: {'āœ… Set' if wix_key else 'āŒ Not set'}") - print(f" ADMIN_API_KEY: {'āœ… Set' if admin_key else 'āŒ Not set'}") - print(f" WIX_WEBHOOK_SECRET: {'āœ… Set' if webhook_secret else 'āŒ Not set'}") - print(f" REDIS_URL: {'āœ… Set' if redis_url else 'āš ļø Optional (using in-memory)'}") - - # Security recommendations - print("\nšŸ›”ļø Security Recommendations:") - if not wix_key: - print(" āŒ Set WIX_API_KEY environment variable") - elif len(wix_key) < 32: - print(" āš ļø WIX_API_KEY should be longer for better security") - else: - print(" āœ… WIX_API_KEY looks secure") - - if not admin_key: - print(" āŒ Set ADMIN_API_KEY environment variable") - elif wix_key and admin_key == wix_key: - print(" āŒ Admin and Wix keys should be different") - else: - print(" āœ… ADMIN_API_KEY configured") - - if not webhook_secret: - print(" āš ļø Consider setting WIX_WEBHOOK_SECRET for signature validation") - else: - print(" āœ… Webhook signature validation enabled") - - print("\nšŸš€ Production Checklist:") - print(" - Use HTTPS in production") - print(" - Set up Redis for distributed rate limiting") - print(" - Configure proper CORS origins") - print(" - Set up monitoring and logging") - print(" - Regular key rotation") - - -if __name__ == "__main__": - print("šŸ” Wix Form Handler API - Security Setup") - print("=" * 50) - - choice = input( - "Choose an option:\n1. Generate new API keys\n2. Check current setup\n\nEnter choice (1 or 2): " - ).strip() - - if choice == "1": - generate_secure_keys() - elif choice == "2": - check_security_setup() - else: - print("Invalid choice. Please run again and choose 1 or 2.") diff --git a/src/alpine_bits_python/scripts/test_api.py b/src/alpine_bits_python/scripts/test_api.py deleted file mode 100644 index 407ce4c..0000000 --- a/src/alpine_bits_python/scripts/test_api.py +++ /dev/null @@ -1,219 +0,0 @@ -#!/usr/bin/env python3 -"""Test script for the Secure Wix Form Handler API -""" - -import asyncio -import os -import sys -from datetime import datetime - -import aiohttp - -# Add parent directory to path to import from src -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -# API Configuration -BASE_URL = "http://localhost:8000" - -# API Keys for testing - replace with your actual keys -TEST_API_KEY = os.getenv("WIX_API_KEY", "sk_live_your_secure_api_key_here") -ADMIN_API_KEY = os.getenv("ADMIN_API_KEY", "sk_admin_your_admin_key_here") - -# Sample Wix form data based on your example -SAMPLE_WIX_DATA = { - "formName": "Contact Form", - "submissions": [], - "submissionTime": "2024-03-20T10:30:00+00:00", - "formFieldMask": ["email", "name", "phone"], - "submissionId": "test-submission-123", - "contactId": "test-contact-456", - "submissionsLink": "https://www.wix.app/forms/test-form/submissions", - "submissionPdf": { - "url": "https://example.com/submission.pdf", - "filename": "submission.pdf", - }, - "formId": "test-form-789", - "field:email_5139": "test@example.com", - "field:first_name_abae": "John", - "field:last_name_d97c": "Doe", - "field:phone_4c77": "+1234567890", - "field:anrede": "Herr", - "field:anzahl_kinder": "2", - "field:alter_kind_3": "8", - "field:alter_kind_4": "12", - "field:long_answer_3524": "This is a long answer field with more details about the inquiry.", - "contact": { - "name": {"first": "John", "last": "Doe"}, - "email": "test@example.com", - "locale": "de", - "company": "Test Company", - "birthdate": "1985-05-15", - "labelKeys": {}, - "contactId": "test-contact-456", - "address": { - "street": "Test Street 123", - "city": "Test City", - "country": "Germany", - "postalCode": "12345", - }, - "jobTitle": "Manager", - "phone": "+1234567890", - "createdDate": "2024-03-20T10:00:00.000Z", - "updatedDate": "2024-03-20T10:30:00.000Z", - }, -} - - -async def test_api(): - """Test the API endpoints with authentication""" - headers_with_auth = { - "Content-Type": "application/json", - "Authorization": f"Bearer {TEST_API_KEY}", - } - - admin_headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {ADMIN_API_KEY}", - } - - async with aiohttp.ClientSession() as session: - # Test health endpoint (no auth required) - print("1. Testing health endpoint (no auth)...") - try: - async with session.get(f"{BASE_URL}/api/health") as response: - result = await response.json() - print(f" āœ… Health check: {response.status} - {result.get('status')}") - except Exception as e: - print(f" āŒ Health check failed: {e}") - - # Test root endpoint (no auth required) - print("\n2. Testing root endpoint (no auth)...") - try: - async with session.get(f"{BASE_URL}/api/") as response: - result = await response.json() - print(f" āœ… Root: {response.status} - {result.get('message')}") - except Exception as e: - print(f" āŒ Root endpoint failed: {e}") - - # Test webhook endpoint without auth (should fail) - print("\n3. Testing webhook endpoint WITHOUT auth (should fail)...") - try: - async with session.post( - f"{BASE_URL}/api/webhook/wix-form", - json=SAMPLE_WIX_DATA, - headers={"Content-Type": "application/json"}, - ) as response: - result = await response.json() - if response.status == 401: - print( - f" āœ… Correctly rejected: {response.status} - {result.get('detail')}" - ) - else: - print(f" āŒ Unexpected response: {response.status} - {result}") - except Exception as e: - print(f" āŒ Test failed: {e}") - - # Test webhook endpoint with valid auth - print("\n4. Testing webhook endpoint WITH valid auth...") - try: - async with session.post( - f"{BASE_URL}/api/webhook/wix-form", - json=SAMPLE_WIX_DATA, - headers=headers_with_auth, - ) as response: - result = await response.json() - if response.status == 200: - print( - f" āœ… Webhook success: {response.status} - {result.get('status')}" - ) - else: - print(f" āŒ Webhook failed: {response.status} - {result}") - except Exception as e: - print(f" āŒ Webhook test failed: {e}") - - # Test test endpoint with auth - print("\n5. Testing simple test endpoint WITH auth...") - try: - async with session.post( - f"{BASE_URL}/api/webhook/wix-form/test", - json={"test": "data", "timestamp": datetime.now().isoformat()}, - headers=headers_with_auth, - ) as response: - result = await response.json() - if response.status == 200: - print( - f" āœ… Test endpoint: {response.status} - {result.get('status')}" - ) - else: - print(f" āŒ Test endpoint failed: {response.status} - {result}") - except Exception as e: - print(f" āŒ Test endpoint failed: {e}") - - # Test rate limiting by making multiple rapid requests - print("\n6. Testing rate limiting (making 5 rapid requests)...") - rate_limit_test_count = 0 - for i in range(5): - try: - async with session.get(f"{BASE_URL}/api/health") as response: - if response.status == 200: - rate_limit_test_count += 1 - elif response.status == 429: - print(f" āœ… Rate limit triggered on request {i + 1}") - break - except Exception as e: - print(f" āŒ Rate limit test failed: {e}") - break - - if rate_limit_test_count == 5: - print(" ā„¹ļø No rate limit reached (normal for low request volume)") - - # Test admin endpoint (if admin key is configured) - print("\n7. Testing admin stats endpoint...") - try: - async with session.get( - f"{BASE_URL}/api/admin/stats", headers=admin_headers - ) as response: - result = await response.json() - if response.status == 200: - print( - f" āœ… Admin stats: {response.status} - {result.get('status')}" - ) - elif response.status == 401: - print( - f" āš ļø Admin access denied (API key not configured): {result.get('detail')}" - ) - else: - print(f" āŒ Admin endpoint failed: {response.status} - {result}") - except Exception as e: - print(f" āŒ Admin test failed: {e}") - - -if __name__ == "__main__": - print("šŸ”’ Testing Secure Wix Form Handler API...") - print("=" * 60) - print("šŸ“ API URL:", BASE_URL) - print( - "šŸ”‘ Using API Key:", - TEST_API_KEY[:20] + "..." if len(TEST_API_KEY) > 20 else TEST_API_KEY, - ) - print( - "šŸ” Using Admin Key:", - ADMIN_API_KEY[:20] + "..." if len(ADMIN_API_KEY) > 20 else ADMIN_API_KEY, - ) - print("=" * 60) - print("Make sure the API is running with: python3 run_api.py") - print("-" * 60) - - try: - asyncio.run(test_api()) - print("\n" + "=" * 60) - print("āœ… Testing completed!") - print("\nšŸ“‹ Quick Setup Reminder:") - print("1. Set environment variables:") - print(" export WIX_API_KEY='your_secure_api_key'") - print(" export ADMIN_API_KEY='your_admin_key'") - print("2. Configure Wix webhook URL: https://yourdomain.com/webhook/wix-form") - print("3. Add Authorization header: Bearer your_api_key") - except Exception as e: - print(f"\nāŒ Error testing API: {e}") - print("Make sure the API server is running!")