diff --git a/app/api/endpoints/calls.py b/app/api/endpoints/calls.py index 676fec2..01dbaa4 100644 --- a/app/api/endpoints/calls.py +++ b/app/api/endpoints/calls.py @@ -1,6 +1,6 @@ import logging -from fastapi import APIRouter, HTTPException, WebSocket, status +from fastapi import APIRouter, HTTPException, WebSocket, status, Request from fastapi.responses import JSONResponse from pydantic import BaseModel @@ -9,6 +9,7 @@ remote_stream_handler) from app.utils.teler_client import TelerClient from app.utils.vapi_client import VapiClient +from app.utils.handling_secrets import verify_teler_signature logger = logging.getLogger(__name__) router = APIRouter() @@ -24,10 +25,19 @@ class CallRequest(BaseModel): to_number: str @router.post("/flow", status_code=status.HTTP_200_OK, include_in_schema=False) -async def stream_flow(payload: CallFlowRequest): +async def stream_flow(request: Request, payload: CallFlowRequest): """ Build and return Stream flow. """ + raw_body = await request.body() + is_valid = verify_teler_signature(request, raw_body) + + if not is_valid: + logger.error("Invalid webhook signature") + logger.error("RAW BODY: %r", raw_body) + raise HTTPException(status_code=401, detail="Invalid signature") + + logger.info("Webhook signature verified") stream_flow = { "action": "stream", @@ -54,7 +64,7 @@ async def initiate_call(call_request: CallRequest): record=True, ) logger.info(f"Call created: {call}") - return JSONResponse(content={"success": True, "call": call}) + return JSONResponse(content={"success": True, "call_id": call.id}) except Exception as e: logger.error(f"Failed to create call: {e}") raise HTTPException( diff --git a/app/api/endpoints/webhooks.py b/app/api/endpoints/webhooks.py index dd79f56..a330591 100644 --- a/app/api/endpoints/webhooks.py +++ b/app/api/endpoints/webhooks.py @@ -1,15 +1,22 @@ import logging - -from fastapi import APIRouter, status +from fastapi import APIRouter, status, Request, HTTPException from fastapi.responses import JSONResponse +from app.utils.handling_secrets import verify_teler_signature logger = logging.getLogger(__name__) router = APIRouter() @router.post("/receiver", status_code=status.HTTP_200_OK, include_in_schema=False) -async def webhook_receiver(data: dict): - """ - Log webhook payload from Teler. - """ - logger.info(f"--------Webhook Payload-------- {data}") - return JSONResponse(content="Webhook received.") +async def webhook_receiver(request: Request, data: dict): + raw_body = await request.body() + is_valid = verify_teler_signature(request, raw_body) + + if not is_valid: + logger.error("Invalid webhook signature") + logger.error("RAW BODY: %r", raw_body) + raise HTTPException(status_code=401, detail="Invalid signature") + + logger.info("Webhook signature verified") + logger.info(f"--------Webhook Payload--------\n{data}") + + return JSONResponse(content="Webhook received") diff --git a/app/core/config.py b/app/core/config.py index c20f7b8..a5ff342 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -28,6 +28,10 @@ def server_domain(self) -> str: # Logging log_level: str = os.getenv("LOG_LEVEL", "INFO") + + # Secret + SECRET: str = os.getenv("SECRET", "") + class Config: env_file = ".env" diff --git a/app/utils/handling_secrets.py b/app/utils/handling_secrets.py new file mode 100644 index 0000000..aec43af --- /dev/null +++ b/app/utils/handling_secrets.py @@ -0,0 +1,40 @@ +import hmac +import hashlib +from fastapi import Request, HTTPException +from app.core.config import settings +import logging + +logger = logging.getLogger(__name__) + +def verify_teler_signature(request: Request, raw_body: bytes) -> bool: + SECRET= settings.SECRET.strip() + + timestamp = request.headers.get("x-teler-timestamp") + received_signature = request.headers.get("x-teler-signature") + + if not timestamp or not received_signature: + raise HTTPException(status_code=400, detail="Missing signature headers") + + proto = request.headers.get("x-forwarded-proto", "http") + host = request.headers.get("x-forwarded-host") or request.headers.get("host") + path = request.url.path + signed_url = f"{proto}://{host}{path}" + method = "POST" + + payload = ( + timestamp.encode() + + b"|" + + method.encode() + + b"|" + + signed_url.encode() + + b"|" + + raw_body + ) + + expected_signature = hmac.new( + SECRET.encode(), + payload, + hashlib.sha256, + ).hexdigest() + + return hmac.compare_digest(expected_signature, received_signature) diff --git a/app/utils/stream_handlers.py b/app/utils/stream_handlers.py index ab611e5..9e30255 100644 --- a/app/utils/stream_handlers.py +++ b/app/utils/stream_handlers.py @@ -108,7 +108,7 @@ async def handler(message: str): logger.debug(f"Buffered message, buffer size: {len(message_buffer)}/{settings.vapi_message_buffer_size}") return ({}, StreamOp.PASS) else: - logger.info(f"VAPI Control: {message}") + logger.debug(f"VAPI Control: {message}") return ({}, StreamOp.PASS) except Exception as e: logger.error(f"Error in remote stream handler: {e}") diff --git a/env.example b/env.example index f621dd9..eefa084 100644 --- a/env.example +++ b/env.example @@ -17,3 +17,6 @@ LOG_LEVEL=INFO # Ngrok Configuration NGROK_AUTHTOKEN=your_ngrok_auth_token_here + +# SECRET +SECRET=your_teler_secret_key \ No newline at end of file