forked from cartsnitch/cartsnitch
fix(receiptwitness): handle invalid timestamp in Mailgun webhook verification
Wrap int(timestamp) in try/except to return False instead of raising ValueError on empty/invalid timestamp, which was causing a 500 error instead of the intended 406. Also add tests for empty timestamp (→ 406) and GET /inbound/email (→ 405). Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -17,7 +17,11 @@ TOKEN_PATTERN = re.compile(r"receipts\+([A-Za-z0-9_-]+)@")
|
||||
|
||||
def verify_mailgun_signature(token: str, timestamp: str, signature: str) -> bool:
|
||||
"""Verify Mailgun webhook signature."""
|
||||
if abs(time.time() - int(timestamp)) > 300: # 5 min freshness
|
||||
try:
|
||||
ts = int(timestamp)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
if abs(time.time() - ts) > 300: # 5 min freshness
|
||||
return False
|
||||
key = settings.mailgun_webhook_signing_key.encode()
|
||||
hmac_digest = hmac.new(key, f"{timestamp}{token}".encode(), hashlib.sha256).hexdigest()
|
||||
|
||||
@@ -99,3 +99,27 @@ def test_stale_timestamp(client, mock_redis):
|
||||
assert response.status_code == 406
|
||||
assert response.json()["detail"] == "Invalid signature"
|
||||
mock_redis["enqueue"].assert_not_awaited()
|
||||
|
||||
|
||||
def test_invalid_timestamp_returns_406(client, mock_redis):
|
||||
"""Empty timestamp should return 406, not 500."""
|
||||
with patch("receiptwitness.api.routes.settings") as mock_settings:
|
||||
mock_settings.mailgun_webhook_signing_key = "test-secret"
|
||||
form = {
|
||||
"token": "test-token",
|
||||
"timestamp": "",
|
||||
"signature": "any-sig",
|
||||
"sender": "sender@example.com",
|
||||
"recipient": "receipts+user123@example.com",
|
||||
"subject": "Receipt",
|
||||
}
|
||||
response = client.post("/inbound/email", data=form)
|
||||
assert response.status_code == 406
|
||||
assert response.json()["detail"] == "Invalid signature"
|
||||
mock_redis["enqueue"].assert_not_awaited()
|
||||
|
||||
|
||||
def test_get_inbound_email_returns_405(client):
|
||||
"""GET /inbound/email is not allowed."""
|
||||
response = client.get("/inbound/email")
|
||||
assert response.status_code == 405
|
||||
|
||||
Reference in New Issue
Block a user