This commit is contained in:
debu-2 2026-10-13 02:01:13 +05:30
parent 9dbda69db1
commit a2b076e9a1
6 changed files with 122 additions and 8 deletions

View file

@ -9,7 +9,13 @@ from sqlalchemy.orm import Session
from .auth import current_user
from .config import settings
from .database import get_db
from .models import UserAccount, WhatsAppBusinessAccount, WhatsAppPhoneAsset, WhatsAppWebhookEvent
from .models import (
UserAccount,
WhatsAppBusinessAccount,
WhatsAppMetaConnection,
WhatsAppPhoneAsset,
WhatsAppWebhookEvent,
)
from .whatsapp_graph_client import WhatsAppGraphClient, WhatsAppGraphError
from .whatsapp_config import resolved_whatsapp_app_secret
from .whatsapp_onboarding import (
@ -40,6 +46,25 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v1/whatsapp", tags=["whatsapp"])
def _webhook_secrets(db: Session) -> list[str]:
"""Platform secret for the admin app, plus each customer's own Meta app secret."""
found: list[str] = []
primary = resolved_whatsapp_app_secret(db).strip()
if primary:
found.append(primary)
extras = db.scalars(
select(WhatsAppMetaConnection.meta_app_secret).where(
WhatsAppMetaConnection.meta_app_secret.is_not(None),
WhatsAppMetaConnection.status != "disconnected",
)
).all()
for item in extras:
value = (item or "").strip()
if value and value not in found:
found.append(value)
return found
def _serialize_event(event: WhatsAppWebhookEvent) -> dict:
return {
"id": str(event.id),
@ -84,10 +109,10 @@ async def receive_webhook(
) -> dict[str, str]:
"""Receive inbound WhatsApp Business Platform webhook payloads."""
raw_body = await request.body()
app_secret = resolved_whatsapp_app_secret(db)
secrets = _webhook_secrets(db)
if app_secret:
if not verify_meta_signature(app_secret, raw_body, x_hub_signature_256):
if secrets:
if not any(verify_meta_signature(secret, raw_body, x_hub_signature_256) for secret in secrets):
logger.warning("WhatsApp webhook signature validation failed")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid signature")
elif settings.environment == "production":
@ -179,6 +204,7 @@ class ManualTokenRequest(BaseModel):
access_token: str = Field(min_length=1, max_length=4096)
waba_id: str = Field(min_length=1, max_length=64)
phone_number_id: str | None = Field(default=None, max_length=64)
app_secret: str | None = Field(default=None, max_length=256)
class SignupCompleteRequest(BaseModel):
@ -227,6 +253,7 @@ async def whatsapp_connect_token(
payload.access_token,
payload.waba_id,
payload.phone_number_id,
payload.app_secret,
)