74 lines
2.5 KiB
Python
74 lines
2.5 KiB
Python
|
|
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)
|