ForcePilot/backend/package/yuxi/channel/extensions/messenger/webhook.py

74 lines
2.5 KiB
Python
Raw Normal View History

import hashlib
import hmac
import json
import logging
from fastapi import APIRouter, Request, HTTPException, Query
from fastapi.responses import PlainTextResponse, JSONResponse
router = APIRouter(prefix="/api/channel/messenger", tags=["messenger"])
logger = logging.getLogger(__name__)
@router.get("/webhook")
async def verify_webhook(
hub_mode: str = Query(None, alias="hub.mode"),
hub_challenge: str = Query(None, alias="hub.challenge"),
hub_verify_token: str = Query(None, alias="hub.verify_token"),
):
from yuxi.channel.extensions.messenger.config import MessengerConfigAdapter
adapter = MessengerConfigAdapter()
account = adapter._build_account("default", {})
verify_token = account.get("verify_token", "")
if not verify_token:
raise HTTPException(status_code=500, detail="verify_token not configured")
if hub_mode == "subscribe" and hub_verify_token == verify_token:
logger.info("messenger webhook verified successfully")
return PlainTextResponse(hub_challenge or "", status_code=200)
logger.warning("messenger webhook verification failed: token mismatch")
raise HTTPException(status_code=403, detail="Verification failed")
@router.post("/webhook")
async def receive_webhook(request: Request):
body = await request.body()
if not _verify_signature(request.headers, body):
logger.warning("messenger webhook signature verification failed")
raise HTTPException(status_code=403, detail="Invalid signature")
payload = json.loads(body)
logger.debug(f"messenger webhook received: entries={len(payload.get('entry', []))}")
from yuxi.channel.extensions.messenger.gateway import _get_webhook_queue
queue = _get_webhook_queue()
if queue is not None:
await queue.put(payload)
return JSONResponse({"status": "ok"})
def _verify_signature(headers, body: bytes) -> bool:
from yuxi.channel.extensions.messenger.config import MessengerConfigAdapter
adapter = MessengerConfigAdapter()
account = adapter._build_account("default", {})
app_secret = account.get("app_secret", "")
if not app_secret:
logger.warning("messenger app_secret not set, skipping signature verification")
return True
expected = headers.get("X-Hub-Signature-256", "")
if not expected.startswith("sha256="):
return False
actual = hmac.new(app_secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={actual}", expected)