diff --git a/.env.example b/.env.example index ab6d031..a65a2d3 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,8 @@ SECRET_KEY = 'my-secret-key' GEMINI_API_KEY = 'my-gemini-api-key' + +# GitHub App Configuration +GITHUB_APP_ID = 'your-github-app-id' +GITHUB_APP_PRIVATE_KEY_PATH = '/path/to/your-private-key.pem' +GITHUB_WEBHOOK_SECRET = 'your-webhook-secret' +GITHUB_APP_INSTALLATION_ID = 'your-installation-id' diff --git a/.gitignore b/.gitignore index 27ca1ad..83dccc8 100644 --- a/.gitignore +++ b/.gitignore @@ -106,6 +106,9 @@ ENV/ env.bak/ venv.bak/ +# GitHub App private keys +*.pem + # mkdocs documentation /site diff --git a/GITHUB_APP_SETUP.md b/GITHUB_APP_SETUP.md new file mode 100644 index 0000000..314a403 --- /dev/null +++ b/GITHUB_APP_SETUP.md @@ -0,0 +1,197 @@ +# GitHub App Setup Guide + +This guide will walk you through setting up a GitHub App for Toasty and testing it with a ping/pong command. + +## Prerequisites + +- A GitHub account +- Access to create GitHub Apps (organization owner or personal account) +- A server or local environment where Toasty is running +- A publicly accessible URL for webhooks (use ngrok for local testing) + +## Step 1: Create a GitHub App + +1. Navigate to GitHub Settings: + - For personal account: Go to **Settings** > **Developer settings** > **GitHub Apps** + - For organization: Go to **Organization Settings** > **Developer settings** > **GitHub Apps** + +2. Click **New GitHub App** + +3. Fill in the basic information: + - **GitHub App name**: `Toasty-YourName` (must be unique across GitHub) + - **Homepage URL**: Your repository URL or `https://github.com/OWASP-BLT/Toasty` + - **Webhook URL**: `https://your-domain.com/aibot/webhook/` + - For local testing with ngrok: `https://your-ngrok-url.ngrok.io/aibot/webhook/` + - **Webhook secret**: Generate a strong random string (save this for later) + ```bash + python -c "import secrets; print(secrets.token_hex(32))" + ``` + +4. Set permissions (under "Repository permissions"): + - **Issues**: Read & Write (for responding to issue comments) + - **Pull requests**: Read & Write (for code review comments) + - **Contents**: Read-only (for accessing code) + - **Metadata**: Read-only (mandatory) + +5. Subscribe to events (under "Subscribe to events"): + - [x] Issue comment + - [x] Issues + - [x] Pull request + - [x] Pull request review + - [x] Pull request review comment + +6. Set "Where can this GitHub App be installed?": + - Choose **Only on this account** (for testing) + +7. Click **Create GitHub App** + +## Step 2: Generate and Download Private Key + +1. After creating the app, scroll down to the **Private keys** section +2. Click **Generate a private key** +3. A `.pem` file will be downloaded - **keep this secure!** +4. Save it in a secure location (do NOT commit it to the repository) + +## Step 3: Install the GitHub App + +1. From your GitHub App settings page, click **Install App** in the left sidebar +2. Select the account/organization where you want to install it +3. Choose either: + - **All repositories** (not recommended for testing) + - **Only select repositories** (recommended) - select the repository you want to use for testing + +4. Click **Install** + +5. Note the **Installation ID** from the URL (e.g., `https://github.com/settings/installations/12345678`) + +## Step 4: Configure Environment Variables + +1. Copy `.env.example` to `.env`: + ```bash + cp .env.example .env + ``` + +2. Add the following to your `.env` file: + ```env + SECRET_KEY=your-django-secret-key + GEMINI_API_KEY=your-gemini-api-key + + # GitHub App Configuration + GITHUB_APP_ID=your-app-id + GITHUB_APP_PRIVATE_KEY_PATH=/path/to/your-private-key.pem + GITHUB_WEBHOOK_SECRET=your-webhook-secret + GITHUB_APP_INSTALLATION_ID=your-installation-id + ``` + + Where: + - `GITHUB_APP_ID`: Found on your GitHub App settings page (e.g., `123456`) + - `GITHUB_APP_PRIVATE_KEY_PATH`: Absolute path to the `.pem` file you downloaded + - `GITHUB_WEBHOOK_SECRET`: The webhook secret you generated in Step 1 + - `GITHUB_APP_INSTALLATION_ID`: The installation ID from Step 3 + + Alternatively, you can set the private key directly as a string: + ```env + GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----" + ``` + +## Step 5: Set Up Local Development with ngrok (Optional) + +If you're testing locally, you'll need to expose your local server to the internet: + +1. Install ngrok: https://ngrok.com/download + +2. Start your Django server: + ```bash + poetry run python manage.py runserver + ``` + +3. In another terminal, start ngrok: + ```bash + ngrok http 8000 + ``` + +4. Copy the HTTPS forwarding URL (e.g., `https://abc123.ngrok.io`) + +5. Update your GitHub App's webhook URL: + - Go to your GitHub App settings + - Update **Webhook URL** to: `https://abc123.ngrok.io/aibot/webhook/` + - Click **Save changes** + +## Step 6: Test the Setup with Ping/Pong + +Now let's verify everything is working: + +1. Make sure your server is running: + ```bash + poetry run python manage.py runserver + ``` + +2. In any issue or pull request in your installed repository, create a comment: + ``` + /ping + ``` + +3. The bot should respond with: + ``` + 🏓 Pong! The GitHub App is working correctly. + ``` + +## Troubleshooting + +### Webhook not receiving events + +1. Check your GitHub App's webhook delivery page: + - Go to your GitHub App settings + - Click **Advanced** tab + - Check **Recent Deliveries** + - Look for failed deliveries and error messages + +2. Verify your webhook URL is publicly accessible: + ```bash + curl -I https://your-webhook-url.com/aibot/webhook/ + ``` + +3. Check your server logs for errors + +### Authentication errors + +1. Verify your `.pem` file is valid: + ```bash + openssl rsa -in your-private-key.pem -check + ``` + +2. Ensure your `GITHUB_APP_ID` is correct (it's a number, not the app name) + +3. Verify the webhook secret matches what you set in GitHub + +### Bot not responding to commands + +1. Check the server logs for errors +2. Verify the app has the correct permissions (Issues: Read & Write) +3. Make sure the app is installed on the correct repository +4. Try the ping command in a new comment (not editing an existing one) + +## Security Best Practices + +1. **Never commit your private key** - Add `*.pem` to `.gitignore` +2. **Use environment variables** - Don't hardcode secrets +3. **Rotate your webhook secret** periodically +4. **Use HTTPS only** for webhook URLs +5. **Validate webhook signatures** - The app does this automatically +6. **Limit permissions** - Only grant what's necessary +7. **Monitor webhook deliveries** - Check for suspicious activity + +## Next Steps + +Now that your GitHub App is set up and working: + +1. Explore the code in `aibot/views.py` to understand how webhooks are handled +2. Add custom commands beyond `/ping` +3. Implement code review features using the AI models +4. Configure CI/CD for automatic deployment + +## Resources + +- [GitHub Apps Documentation](https://docs.github.com/en/developers/apps) +- [Webhook Events and Payloads](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads) +- [Authenticating as a GitHub App](https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps) diff --git a/README.md b/README.md index 8dc5f8e..fc8909e 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ # Toasty + +The smart, context aware AI code reviewer from OWASP BLT. + +## Documentation + +- [GitHub App Setup Guide](GITHUB_APP_SETUP.md) - Instructions for setting up a GitHub App and linking it to this repository diff --git a/aibot/github_utils.py b/aibot/github_utils.py new file mode 100644 index 0000000..24c5625 --- /dev/null +++ b/aibot/github_utils.py @@ -0,0 +1,127 @@ +""" +Utilities for GitHub App authentication and API interactions. +""" + +import hashlib +import hmac +import time +from pathlib import Path + +import jwt +import requests +from django.conf import settings + + +class GitHubAppAuth: + """Handle GitHub App authentication and API requests.""" + + def __init__(self): + # Validate required settings + if not settings.GITHUB_APP_ID: + raise ValueError("GITHUB_APP_ID environment variable is required") + if not settings.GITHUB_APP_INSTALLATION_ID: + raise ValueError("GITHUB_APP_INSTALLATION_ID environment variable is required") + + self.app_id = str(settings.GITHUB_APP_ID) + self.private_key = self._load_private_key() + self.installation_id = settings.GITHUB_APP_INSTALLATION_ID + + def _load_private_key(self): + """Load the private key from file or environment variable.""" + # Try to load from environment variable first + if settings.GITHUB_APP_PRIVATE_KEY: + return settings.GITHUB_APP_PRIVATE_KEY.replace("\\n", "\n") + + # Otherwise load from file + if settings.GITHUB_APP_PRIVATE_KEY_PATH: + key_path = Path(settings.GITHUB_APP_PRIVATE_KEY_PATH) + if key_path.exists(): + return key_path.read_text() + + raise ValueError("GitHub App private key not found in environment variables or file path") + + def generate_jwt(self): + """Generate a JWT for authenticating as a GitHub App.""" + now = int(time.time()) + payload = { + "iat": now - 60, # Issued at time (60 seconds in the past to allow for clock drift) + "exp": now + (10 * 60), # Expiration time (10 minutes from now) + "iss": self.app_id, + } + + return jwt.encode(payload, self.private_key, algorithm="RS256") + + def get_installation_access_token(self): + """Get an installation access token for making API requests.""" + jwt_token = self.generate_jwt() + + headers = { + "Authorization": f"Bearer {jwt_token}", + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + } + + url = f"https://api.github.com/app/installations/{self.installation_id}/access_tokens" + try: + response = requests.post(url, headers=headers, timeout=10) + response.raise_for_status() + return response.json()["token"] + except requests.exceptions.HTTPError as e: + if e.response.status_code == 401: + raise ValueError("GitHub App authentication failed. Check your App ID and private key.") from e + elif e.response.status_code == 404: + raise ValueError("GitHub App installation not found. Check your installation ID.") from e + else: + raise ValueError(f"GitHub API error: {e.response.status_code}") from e + + def create_comment(self, owner, repo, issue_number, body): + """Create a comment on an issue or pull request.""" + token = self.get_installation_access_token() + + headers = { + "Authorization": f"Bearer {token}", + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + } + + url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}/comments" + data = {"body": body} + + try: + response = requests.post(url, headers=headers, json=data, timeout=10) + response.raise_for_status() + return response.json() + except requests.exceptions.HTTPError as e: + if e.response.status_code == 403: + raise ValueError("GitHub App lacks permission to comment. Check repository permissions.") from e + elif e.response.status_code == 404: + raise ValueError(f"Repository or issue not found: {owner}/{repo}#{issue_number}") from e + else: + raise ValueError(f"GitHub API error: {e.response.status_code}") from e + + +def verify_webhook_signature(request): + """ + Verify that the webhook request came from GitHub. + + Args: + request: Django HttpRequest object + + Returns: + bool: True if signature is valid, False otherwise + """ + signature_header = request.headers.get("X-Hub-Signature-256") + if not signature_header: + return False + + webhook_secret = settings.GITHUB_WEBHOOK_SECRET + if not webhook_secret: + return False + + # Calculate expected signature + expected_signature = "sha256=" + hmac.new( + webhook_secret.encode("utf-8"), request.body, hashlib.sha256 + ).hexdigest() + + # Use constant-time comparison to prevent timing attacks + return hmac.compare_digest(signature_header, expected_signature) diff --git a/aibot/tests.py b/aibot/tests.py index a39b155..4fc2ef8 100644 --- a/aibot/tests.py +++ b/aibot/tests.py @@ -1 +1,157 @@ -# Create your tests here. +""" +Tests for the GitHub App webhook functionality. +""" + +import hashlib +import hmac +import json +from unittest.mock import Mock, patch + +from django.conf import settings +from django.test import TestCase, override_settings + + +class WebhookViewTests(TestCase): + """Test cases for the GitHub webhook endpoint.""" + + def setUp(self): + """Set up test data.""" + self.webhook_url = "/aibot/webhook/" + self.webhook_secret = "test_webhook_secret" + + def _create_signature(self, payload): + """Create a valid GitHub webhook signature.""" + signature = "sha256=" + hmac.new( + self.webhook_secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256 + ).hexdigest() + return signature + + @override_settings(GITHUB_WEBHOOK_SECRET="test_webhook_secret") + def test_ping_event(self): + """Test that ping events return pong.""" + payload = json.dumps({"zen": "Keep it simple"}) + signature = self._create_signature(payload) + + response = self.client.post( + self.webhook_url, + data=payload, + content_type="application/json", + HTTP_X_GITHUB_EVENT="ping", + HTTP_X_HUB_SIGNATURE_256=signature, + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data["message"], "Pong!") + + @override_settings(GITHUB_WEBHOOK_SECRET="test_webhook_secret") + def test_invalid_signature(self): + """Test that invalid signatures are rejected.""" + payload = json.dumps({"action": "created"}) + + response = self.client.post( + self.webhook_url, + data=payload, + content_type="application/json", + HTTP_X_GITHUB_EVENT="issue_comment", + HTTP_X_HUB_SIGNATURE_256="sha256=invalid", + ) + + self.assertEqual(response.status_code, 401) + data = response.json() + self.assertEqual(data["error"], "Invalid signature") + + @override_settings(GITHUB_WEBHOOK_SECRET="test_webhook_secret") + def test_missing_signature(self): + """Test that requests without signatures are rejected.""" + payload = json.dumps({"action": "created"}) + + response = self.client.post( + self.webhook_url, data=payload, content_type="application/json", HTTP_X_GITHUB_EVENT="issue_comment" + ) + + self.assertEqual(response.status_code, 401) + + @override_settings( + GITHUB_WEBHOOK_SECRET="test_webhook_secret", + GITHUB_APP_ID="123456", + GITHUB_APP_INSTALLATION_ID="7891011", + ) + @patch("aibot.views.GitHubAppAuth") + def test_ping_command(self, mock_github_auth): + """Test that /ping command triggers a response.""" + # Mock the GitHub API call + mock_auth_instance = Mock() + mock_github_auth.return_value = mock_auth_instance + + payload = json.dumps( + { + "action": "created", + "comment": {"body": "/ping"}, + "issue": {"number": 123}, + "repository": {"name": "test-repo", "owner": {"login": "test-owner"}}, + } + ) + signature = self._create_signature(payload) + + response = self.client.post( + self.webhook_url, + data=payload, + content_type="application/json", + HTTP_X_GITHUB_EVENT="issue_comment", + HTTP_X_HUB_SIGNATURE_256=signature, + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data["message"], "Pong sent!") + + # Verify that create_comment was called + mock_auth_instance.create_comment.assert_called_once_with( + owner="test-owner", + repo="test-repo", + issue_number=123, + body="🏓 Pong! The GitHub App is working correctly.", + ) + + @override_settings(GITHUB_WEBHOOK_SECRET="test_webhook_secret") + def test_non_ping_comment(self): + """Test that non-command comments are ignored.""" + payload = json.dumps( + { + "action": "created", + "comment": {"body": "This is just a regular comment"}, + "issue": {"number": 123}, + "repository": {"name": "test-repo", "owner": {"login": "test-owner"}}, + } + ) + signature = self._create_signature(payload) + + response = self.client.post( + self.webhook_url, + data=payload, + content_type="application/json", + HTTP_X_GITHUB_EVENT="issue_comment", + HTTP_X_HUB_SIGNATURE_256=signature, + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data["message"], "No command recognized") + + @override_settings(GITHUB_WEBHOOK_SECRET="test_webhook_secret") + def test_invalid_json(self): + """Test that invalid JSON payloads are rejected.""" + signature = self._create_signature("invalid json") + + response = self.client.post( + self.webhook_url, + data="invalid json", + content_type="application/json", + HTTP_X_GITHUB_EVENT="issue_comment", + HTTP_X_HUB_SIGNATURE_256=signature, + ) + + self.assertEqual(response.status_code, 400) + data = response.json() + self.assertEqual(data["error"], "Invalid JSON") diff --git a/aibot/urls.py b/aibot/urls.py index ffeb778..2fbf626 100644 --- a/aibot/urls.py +++ b/aibot/urls.py @@ -3,4 +3,5 @@ urlpatterns = [ path("", views.index, name="index"), + path("webhook/", views.webhook, name="webhook"), ] diff --git a/aibot/views.py b/aibot/views.py index 512b353..c65e1d1 100644 --- a/aibot/views.py +++ b/aibot/views.py @@ -1,5 +1,103 @@ -from django.http import HttpResponse +import json +import logging + +from django.http import HttpResponse, JsonResponse +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods + +from .github_utils import GitHubAppAuth, verify_webhook_signature + +logger = logging.getLogger(__name__) def index(request): return HttpResponse("Hello from the AI Reviewer!") + + +@csrf_exempt +@require_http_methods(["POST"]) +def webhook(request): + """ + Handle incoming webhook events from GitHub. + """ + # Verify webhook signature + if not verify_webhook_signature(request): + logger.warning("Invalid webhook signature") + return JsonResponse({"error": "Invalid signature"}, status=401) + + # Parse the payload + try: + payload = json.loads(request.body) + except json.JSONDecodeError: + logger.error("Invalid JSON payload") + return JsonResponse({"error": "Invalid JSON"}, status=400) + + # Get event type + event_type = request.headers.get("X-GitHub-Event") + logger.info(f"Received {event_type} event") + + # Handle different event types + if event_type == "ping": + return JsonResponse({"message": "Pong!"}) + + elif event_type == "issue_comment": + return handle_issue_comment(payload) + + elif event_type in ["pull_request", "pull_request_review", "pull_request_review_comment"]: + return handle_pull_request(payload) + + # For other events, just acknowledge receipt + return JsonResponse({"message": "Event received"}) + + +def handle_issue_comment(payload): + """ + Handle issue comment events. + Processes commands like /ping. + """ + # Only process new comments + if payload.get("action") != "created": + return JsonResponse({"message": "Not a new comment"}) + + comment_body = payload.get("comment", {}).get("body", "").strip() + issue_number = payload.get("issue", {}).get("number") + repository = payload.get("repository", {}) + owner = repository.get("owner", {}).get("login") + repo_name = repository.get("name") + + logger.info(f"Processing comment on {owner}/{repo_name}#{issue_number}: {comment_body}") + + # Handle /ping command + if comment_body.lower() == "/ping": + try: + github_auth = GitHubAppAuth() + github_auth.create_comment( + owner=owner, + repo=repo_name, + issue_number=issue_number, + body="🏓 Pong! The GitHub App is working correctly.", + ) + return JsonResponse({"message": "Pong sent!"}) + except Exception as e: + logger.error(f"Error sending pong: {e}") + return JsonResponse({"error": "Failed to send response. Check server logs for details."}, status=500) + + return JsonResponse({"message": "No command recognized"}) + + +def handle_pull_request(payload): + """ + Handle pull request events. + This is a placeholder for future AI code review functionality. + """ + action = payload.get("action") + pr_number = payload.get("pull_request", {}).get("number") + repository = payload.get("repository", {}) + owner = repository.get("owner", {}).get("login") + repo_name = repository.get("name") + + logger.info(f"Pull request {action} on {owner}/{repo_name}#{pr_number}") + + # TODO: Implement AI code review logic here + + return JsonResponse({"message": "Pull request event received"}) diff --git a/poetry.lock b/poetry.lock index f8f7c47..2b294ab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "amqp" @@ -42,6 +42,7 @@ files = [ [package.dependencies] idna = ">=2.8" sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.31.0)"] @@ -182,6 +183,104 @@ files = [ {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, ] +[[package]] +name = "cffi" +version = "2.0.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, +] + +[package.dependencies] +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} + [[package]] name = "charset-normalizer" version = "3.4.4" @@ -385,6 +484,66 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "cryptography" +version = "44.0.3" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +groups = ["main"] +files = [ + {file = "cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d"}, + {file = "cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904"}, + {file = "cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44"}, + {file = "cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d"}, + {file = "cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d"}, + {file = "cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f"}, + {file = "cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5"}, + {file = "cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b"}, + {file = "cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028"}, + {file = "cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cad399780053fb383dc067475135e41c9fe7d901a97dd5d9c5dfb5611afc0d7d"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:21a83f6f35b9cc656d71b5de8d519f566df01e660ac2578805ab245ffd8523f8"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fc3c9babc1e1faefd62704bb46a69f359a9819eb0292e40df3fb6e3574715cd4"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:e909df4053064a97f1e6565153ff8bb389af12c5c8d29c343308760890560aff"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dad80b45c22e05b259e33ddd458e9e2ba099c86ccf4e88db7bbab4b747b18d06"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:479d92908277bed6e1a1c69b277734a7771c2b78633c224445b5c60a9f4bc1d9"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c"}, + {file = "cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] +pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test-randomorder = ["pytest-randomly"] + [[package]] name = "django" version = "5.2.8" @@ -1238,6 +1397,19 @@ files = [ [package.dependencies] pyasn1 = ">=0.6.1,<0.7.0" +[[package]] +name = "pycparser" +version = "2.23" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"" +files = [ + {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, + {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, +] + [[package]] name = "pydantic" version = "2.12.4" @@ -1571,7 +1743,10 @@ files = [ [package.dependencies] grpcio = ">=1.41.0" httpx = {version = ">=0.20.0", extras = ["http2"]} -numpy = {version = ">=2.1.0", markers = "python_version >= \"3.13\""} +numpy = [ + {version = ">=1.26", markers = "python_version == \"3.12\""}, + {version = ">=2.1.0", markers = "python_version >= \"3.13\""}, +] portalocker = ">=2.7.0,<4.0" protobuf = ">=3.20.0" pydantic = ">=1.10.8,<2.0.dev0 || >2.2.0" @@ -2171,5 +2346,5 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" -python-versions = ">=3.13,<4.0.0" -content-hash = "2839446535514599cfab96239aa18395c882785fbe0471971023dad2c0030443" +python-versions = ">=3.12,<4.0.0" +content-hash = "5fc2de1078ffd7e10f210e352e1407c40f4e0e35673812f439cdd4e152ff4364" diff --git a/pyproject.toml b/pyproject.toml index 70396c5..d64fac9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = [ {name = "Sahil Dhillon",email = "sodhillon_b21@it.vjti.ac.in"} ] readme = "README.md" -requires-python = ">=3.13,<4.0.0" +requires-python = ">=3.12,<4.0.0" dependencies = [ "django (>=5.2.8,<6.0.0)", "google-genai (>=1.52.0,<2.0.0)", @@ -18,7 +18,9 @@ dependencies = [ "tenacity (>=9.1.2,<10.0.0)", "pyjwt (>=2.10.1,<3.0.0)", "celery[redis] (>=5.5.3,<6.0.0)", - "rich (>=14.2.0,<15.0.0)" + "rich (>=14.2.0,<15.0.0)", + "requests (>=2.32.3,<3.0.0)", + "cryptography (>=44.0.0,<45.0.0)" ] diff --git a/toasty/settings.py b/toasty/settings.py index ad8a8e8..14dab9e 100644 --- a/toasty/settings.py +++ b/toasty/settings.py @@ -1,4 +1,5 @@ import os +import warnings from pathlib import Path @@ -6,6 +7,16 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = os.getenv("SECRET_KEY") +if not SECRET_KEY: + SECRET_KEY = "django-insecure-test-key-for-development-only" + warnings.warn("SECRET_KEY not set in environment. Using default (insecure for production).", UserWarning) + +# GitHub App Configuration +GITHUB_APP_ID = os.getenv("GITHUB_APP_ID") +GITHUB_APP_PRIVATE_KEY_PATH = os.getenv("GITHUB_APP_PRIVATE_KEY_PATH") +GITHUB_APP_PRIVATE_KEY = os.getenv("GITHUB_APP_PRIVATE_KEY") +GITHUB_WEBHOOK_SECRET = os.getenv("GITHUB_WEBHOOK_SECRET") +GITHUB_APP_INSTALLATION_ID = os.getenv("GITHUB_APP_INSTALLATION_ID") DEBUG = True @@ -34,6 +45,9 @@ "django.middleware.clickjacking.XFrameOptionsMiddleware", ] +# Allowed hosts for webhook (update for production) +CSRF_TRUSTED_ORIGINS = [] + ROOT_URLCONF = "toasty.urls" TEMPLATES = [