mirror of https://github.com/kortix-ai/suna.git
102 lines
4.1 KiB
Python
102 lines
4.1 KiB
Python
import hmac
|
|
import hashlib
|
|
import time
|
|
import json
|
|
from typing import Dict, Any, Optional
|
|
from fastapi import HTTPException
|
|
from .models import SlackEventRequest, SlackWebhookPayload
|
|
from utils.logger import logger
|
|
|
|
class SlackWebhookProvider:
|
|
"""Handles Slack webhook events and verification."""
|
|
|
|
@staticmethod
|
|
def verify_signature(body: bytes, timestamp: str, signature: str, signing_secret: str) -> bool:
|
|
"""Verify Slack request signature."""
|
|
try:
|
|
basestring = f"v0:{timestamp}:{body.decode('utf-8')}"
|
|
my_signature = f"v0={hmac.new(signing_secret.encode(), basestring.encode(), hashlib.sha256).hexdigest()}"
|
|
return hmac.compare_digest(my_signature, signature)
|
|
except Exception as e:
|
|
logger.error(f"Error verifying Slack signature: {e}")
|
|
return False
|
|
|
|
@staticmethod
|
|
def validate_request_timing(timestamp: str, tolerance: int = 300) -> bool:
|
|
"""Validate that the request is not too old (replay attack protection)."""
|
|
try:
|
|
request_time = int(timestamp)
|
|
current_time = int(time.time())
|
|
return abs(current_time - request_time) < tolerance
|
|
except (ValueError, TypeError):
|
|
return False
|
|
|
|
@staticmethod
|
|
def process_event(event_data: SlackEventRequest) -> Optional[SlackWebhookPayload]:
|
|
"""Process Slack event and extract relevant data."""
|
|
try:
|
|
# Handle cases where type might be None (empty verification requests)
|
|
if not event_data.type:
|
|
logger.info("Slack event has no type, likely verification ping")
|
|
return None
|
|
|
|
if event_data.type == "url_verification":
|
|
return None
|
|
|
|
if event_data.type == "event_callback" and event_data.event:
|
|
event = event_data.event
|
|
event_type = event.get("type")
|
|
|
|
if event_type == "app_mention":
|
|
text = event.get("text", "")
|
|
user_id = event.get("user", "")
|
|
channel_id = event.get("channel", "")
|
|
timestamp = event.get("ts", "")
|
|
import re
|
|
clean_text = re.sub(r'<@[^>]+>', '', text).strip()
|
|
|
|
return SlackWebhookPayload(
|
|
text=clean_text,
|
|
user_id=user_id,
|
|
channel_id=channel_id,
|
|
team_id=event_data.team_id or "",
|
|
timestamp=timestamp,
|
|
event_type=event_type,
|
|
trigger_word="mention"
|
|
)
|
|
|
|
elif event_type == "message":
|
|
if event.get("channel_type") == "im":
|
|
text = event.get("text", "")
|
|
user_id = event.get("user", "")
|
|
channel_id = event.get("channel", "")
|
|
timestamp = event.get("ts", "")
|
|
|
|
return SlackWebhookPayload(
|
|
text=text,
|
|
user_id=user_id,
|
|
channel_id=channel_id,
|
|
team_id=event_data.team_id or "",
|
|
timestamp=timestamp,
|
|
event_type=event_type,
|
|
trigger_word="direct_message"
|
|
)
|
|
|
|
logger.warning(f"Unhandled Slack event type: {event_data.type}")
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing Slack event: {e}")
|
|
raise HTTPException(status_code=400, detail=f"Error processing Slack event: {str(e)}")
|
|
|
|
class GenericWebhookProvider:
|
|
"""Handles generic webhook events."""
|
|
|
|
@staticmethod
|
|
def process_payload(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Process generic webhook payload."""
|
|
return {
|
|
"payload": data,
|
|
"trigger_type": "webhook",
|
|
"processed_at": time.time()
|
|
} |