suna/backend/triggers/oauth_api.py

198 lines
7.0 KiB
Python

from fastapi import APIRouter, HTTPException, Depends, Query
from fastapi.responses import RedirectResponse
from typing import Optional
from pydantic import BaseModel
from .providers.slack_oauth import SlackOAuthManager
from services.supabase import DBConnection
from utils.auth_utils import get_current_user_id_from_jwt
from utils.logger import logger
router = APIRouter(prefix="/api/integrations/slack", tags=["slack-oauth"])
slack_oauth_manager: Optional[SlackOAuthManager] = None
db = None
def initialize(database: DBConnection):
"""Initialize the Slack OAuth API with database connection."""
global db, slack_oauth_manager
db = database
slack_oauth_manager = SlackOAuthManager(db)
class SlackInstallRequest(BaseModel):
agent_id: str
class SlackInstallResponse(BaseModel):
install_url: str
state: str
class SlackCallbackResponse(BaseModel):
success: bool
trigger_id: Optional[str] = None
workspace_name: Optional[str] = None
bot_name: Optional[str] = None
webhook_url: Optional[str] = None
error: Optional[str] = None
@router.post("/install", response_model=SlackInstallResponse)
async def initiate_slack_install(
request: SlackInstallRequest,
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Initiate Slack OAuth flow for agent installation."""
if not slack_oauth_manager:
raise HTTPException(status_code=500, detail="Slack OAuth not initialized")
try:
await verify_agent_access(request.agent_id, user_id)
install_url = slack_oauth_manager.generate_install_url(request.agent_id, user_id)
from urllib.parse import urlparse, parse_qs
parsed_url = urlparse(install_url)
state = parse_qs(parsed_url.query).get('state', [''])[0]
return SlackInstallResponse(
install_url=install_url,
state=state
)
except Exception as e:
logger.error(f"Error initiating Slack install: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/callback")
async def handle_slack_callback(
code: str = Query(..., description="OAuth authorization code"),
state: str = Query(..., description="State parameter"),
error: Optional[str] = Query(None, description="OAuth error")
):
"""Handle Slack OAuth callback."""
if not slack_oauth_manager:
raise HTTPException(status_code=500, detail="Slack OAuth not initialized")
if error:
logger.error(f"Slack OAuth error: {error}")
return RedirectResponse(
url=f"http://localhost:3000/agents?slack_error={error}",
status_code=302
)
try:
result = await slack_oauth_manager.handle_oauth_callback(code, state)
if result["success"]:
redirect_url = (
f"http://localhost:3000/agents?"
f"slack_success=true&"
f"trigger_id={result['trigger_id']}&"
f"workspace={result.get('workspace_name', '')}&"
f"bot_name={result.get('bot_name', '')}"
)
return RedirectResponse(url=redirect_url, status_code=302)
else:
# Redirect to frontend with error
return RedirectResponse(
url=f"http://localhost:3000/agents?slack_error={result['error']}",
status_code=302
)
except Exception as e:
logger.error(f"Error handling Slack callback: {e}")
return RedirectResponse(
url=f"http://localhost:3000/agents?slack_error=callback_failed",
status_code=302
)
@router.get("/status/{agent_id}")
async def get_slack_status(
agent_id: str,
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Get Slack integration status for an agent."""
try:
await verify_agent_access(agent_id, user_id)
client = await db.client
result = await client.table('agent_triggers')\
.select('trigger_id, name, is_active')\
.eq('agent_id', agent_id)\
.eq('trigger_type', 'slack')\
.execute()
slack_triggers = []
for trigger in result.data:
oauth_result = await client.table('slack_oauth_installations')\
.select('team_name, bot_name, installed_at')\
.eq('trigger_id', trigger['trigger_id'])\
.execute()
oauth_data = oauth_result.data[0] if oauth_result.data else {}
slack_triggers.append({
"trigger_id": trigger["trigger_id"],
"name": trigger["name"],
"is_active": trigger["is_active"],
"workspace_name": oauth_data.get("team_name"),
"bot_name": oauth_data.get("bot_name"),
"installed_at": oauth_data.get("installed_at")
})
return {
"agent_id": agent_id,
"has_slack_integration": len(slack_triggers) > 0,
"slack_triggers": slack_triggers
}
except Exception as e:
logger.error(f"Error getting Slack status: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/uninstall/{trigger_id}")
async def uninstall_slack_integration(
trigger_id: str,
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Uninstall Slack integration for a trigger."""
try:
client = await db.client
trigger_result = await client.table('agent_triggers')\
.select('agent_id')\
.eq('trigger_id', trigger_id)\
.execute()
if not trigger_result.data:
raise HTTPException(status_code=404, detail="Trigger not found")
agent_id = trigger_result.data[0]['agent_id']
await verify_agent_access(agent_id, user_id)
from .core import TriggerManager
trigger_manager = TriggerManager(db)
success = await trigger_manager.delete_trigger(trigger_id)
if success:
await client.table('slack_oauth_installations')\
.delete()\
.eq('trigger_id', trigger_id)\
.execute()
return {"success": True, "message": "Slack integration uninstalled"}
else:
raise HTTPException(status_code=500, detail="Failed to uninstall integration")
except Exception as e:
logger.error(f"Error uninstalling Slack integration: {e}")
raise HTTPException(status_code=500, detail=str(e))
async def verify_agent_access(agent_id: str, user_id: str):
"""Verify that the user has access to the agent."""
client = await db.client
result = await client.table('agents').select('account_id').eq('agent_id', agent_id).execute()
if not result.data:
raise HTTPException(status_code=404, detail="Agent not found")
agent = result.data[0]
if agent['account_id'] != user_id:
raise HTTPException(status_code=403, detail="Access denied")