Implement FastAPI webhook receiver for Gitea issue assignment events with HMAC SHA256 signature verification and event routing. Implementation details: - FastAPI application with /webhook/gitea POST endpoint - HMAC SHA256 signature verification in security.py - Event routing for assigned, unassigned, closed actions - Comprehensive logging for all webhook events - Health check endpoint at /health - Docker containerization with health checks - 91% test coverage (exceeds 85% requirement) TDD workflow followed: - Wrote 16 tests first (RED phase) - Implemented features to pass tests (GREEN phase) - All tests passing with 91% coverage - Type checking with mypy: success - Linting with ruff: success Files created: - apps/coordinator/src/main.py - FastAPI application - apps/coordinator/src/webhook.py - Webhook handlers - apps/coordinator/src/security.py - HMAC verification - apps/coordinator/src/config.py - Configuration management - apps/coordinator/tests/ - Comprehensive test suite - apps/coordinator/Dockerfile - Production container - apps/coordinator/pyproject.toml - Python project config Configuration: - Updated .env.example with GITEA_WEBHOOK_SECRET - Updated docker-compose.yml with coordinator service Testing: - 16 unit and integration tests - Security tests for signature verification - Event handler tests for all supported actions - Health check endpoint tests - All tests passing with 91% coverage This unblocks issue #158 (issue parser). Fixes #157 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
36 lines
1015 B
Python
36 lines
1015 B
Python
"""Security utilities for webhook signature verification."""
|
|
|
|
import hashlib
|
|
import hmac
|
|
|
|
|
|
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
|
|
"""
|
|
Verify HMAC SHA256 signature of webhook payload.
|
|
|
|
Args:
|
|
payload: Raw request body as bytes
|
|
signature: Signature from X-Gitea-Signature header
|
|
secret: Webhook secret configured in Gitea
|
|
|
|
Returns:
|
|
True if signature is valid, False otherwise
|
|
|
|
Example:
|
|
>>> payload = b'{"action": "assigned"}'
|
|
>>> secret = "my-webhook-secret"
|
|
>>> sig = hmac.new(secret.encode(), payload, "sha256").hexdigest()
|
|
>>> verify_signature(payload, sig, secret)
|
|
True
|
|
"""
|
|
if not signature:
|
|
return False
|
|
|
|
# Compute expected signature
|
|
expected_signature = hmac.new(
|
|
secret.encode("utf-8"), payload, hashlib.sha256
|
|
).hexdigest()
|
|
|
|
# Use timing-safe comparison to prevent timing attacks
|
|
return hmac.compare_digest(signature, expected_signature)
|