- Nouveaux fichiers: api_tokens_db.py, api_v1/routes/user_tokens.py, api_v1/utils_webhook.py - Migration DB idempotente: tables user_api_tokens + user_webhooks - Endpoints POST/DELETE /api/v1/user/api-token (Pro only) - Endpoints POST/DELETE /api/v1/user/webhook (Pro only, HTTPS requis) - HMAC-SHA256 fire-and-forget dispatch webhook - auth.py: validate_api_key() + X-API-Key fallback dans jwt_required_middleware - saas_auth.py: import logging au niveau module, validate_api_key(), X-API-Key fallback - api_v1/__init__.py: enregistrement user_tokens_bp - 24 tests pytest — tous passent Co-Authored-By: Paperclip <noreply@paperclip.ing>
81 lines
2.1 KiB
Python
81 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
utils_webhook.py — Webhook dispatch utility (fire-and-forget, HMAC-SHA256)
|
|
HRT-80
|
|
"""
|
|
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import logging
|
|
|
|
import requests
|
|
|
|
from api_tokens_db import get_db
|
|
|
|
logger = logging.getLogger("turf_saas.webhook")
|
|
|
|
EVENT_NEW_PREDICTION = "new_prediction"
|
|
EVENT_VALUE_BET = "value_bet"
|
|
|
|
|
|
def dispatch_webhook(user_id: str, event_type: str, payload: dict) -> None:
|
|
"""
|
|
Send HMAC-signed webhook POST to URL configured by user.
|
|
Fire-and-forget: errors logged, never re-raised. Timeout: 5s.
|
|
"""
|
|
conn = get_db()
|
|
try:
|
|
row = conn.execute(
|
|
"SELECT url, secret FROM user_webhooks WHERE user_id = ?",
|
|
(str(user_id),),
|
|
).fetchone()
|
|
except Exception as e:
|
|
logger.warning("dispatch_webhook: DB error for user %s: %s", user_id, e)
|
|
return
|
|
finally:
|
|
conn.close()
|
|
|
|
if not row:
|
|
return
|
|
|
|
url = row["url"]
|
|
secret = row["secret"]
|
|
body = json.dumps(
|
|
{"event": event_type, "data": payload},
|
|
ensure_ascii=False,
|
|
separators=(",", ":"),
|
|
)
|
|
signature = hmac.new(
|
|
secret.encode("utf-8"), body.encode("utf-8"), hashlib.sha256
|
|
).hexdigest()
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"X-Turf-Signature": f"sha256={signature}",
|
|
"X-Turf-Event": event_type,
|
|
"User-Agent": "TurfSaaS-Webhook/1.0",
|
|
}
|
|
try:
|
|
resp = requests.post(url, data=body, headers=headers, timeout=5)
|
|
logger.info(
|
|
"Webhook dispatched to user %s (event=%s, status=%s)",
|
|
user_id,
|
|
event_type,
|
|
resp.status_code,
|
|
)
|
|
except requests.exceptions.Timeout:
|
|
logger.warning(
|
|
"Webhook timeout for user %s (event=%s, url=%s)", user_id, event_type, url
|
|
)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(
|
|
"Webhook failed for user %s (event=%s): %s", user_id, event_type, e
|
|
)
|
|
except Exception as e:
|
|
logger.warning(
|
|
"Webhook unexpected error for user %s (event=%s): %s",
|
|
user_id,
|
|
event_type,
|
|
e,
|
|
)
|