""" Slack OAuth integration for one-click agent installation. This module handles the OAuth flow for installing agents directly into Slack workspaces with automatic configuration and webhook setup. """ import httpx import secrets import json from typing import Dict, Any, Optional from urllib.parse import urlencode import os from utils.logger import logger from services.supabase import DBConnection class SlackOAuthManager: """Manages Slack OAuth flow for agent installation.""" def __init__(self, db_connection: DBConnection): self.db = db_connection self.client_id = os.getenv("SLACK_CLIENT_ID") self.client_secret = os.getenv("SLACK_CLIENT_SECRET") self.redirect_uri = os.getenv("SLACK_REDIRECT_URI", "http://localhost:3000/integrations/slack/callback") def generate_install_url(self, agent_id: str, user_id: str) -> str: """Generate Slack app installation URL with state parameter.""" state = self._create_state_token(agent_id, user_id) params = { "client_id": self.client_id, "scope": "app_mentions:read,channels:read,chat:write,im:read,im:write,users:read", "redirect_uri": self.redirect_uri, "state": state } return f"https://slack.com/oauth/v2/authorize?{urlencode(params)}" async def handle_oauth_callback(self, code: str, state: str) -> Dict[str, Any]: """Handle OAuth callback and set up agent trigger.""" try: # Verify state and extract agent/user info state_data = await self._verify_state_token(state) if not state_data: return {"success": False, "error": "Invalid state parameter"} agent_id = state_data["agent_id"] user_id = state_data["user_id"] # Exchange code for access token token_data = await self._exchange_code_for_token(code) if not token_data: return {"success": False, "error": "Failed to exchange code for token"} # Get workspace and bot info workspace_info = await self._get_workspace_info(token_data["access_token"]) bot_info = await self._get_bot_info(token_data["bot_user_id"], token_data["access_token"]) # Create trigger configuration trigger_config = await self._create_agent_trigger( agent_id=agent_id, user_id=user_id, workspace_info=workspace_info, bot_info=bot_info, oauth_data=token_data ) return { "success": True, "trigger_id": trigger_config["trigger_id"], "workspace_name": workspace_info.get("team", {}).get("name"), "bot_name": bot_info.get("name"), "webhook_url": trigger_config["webhook_url"] } except Exception as e: logger.error(f"Error handling Slack OAuth callback: {e}") return {"success": False, "error": str(e)} async def _exchange_code_for_token(self, code: str) -> Optional[Dict[str, Any]]: """Exchange OAuth code for access token.""" try: async with httpx.AsyncClient() as client: response = await client.post( "https://slack.com/api/oauth.v2.access", data={ "client_id": self.client_id, "client_secret": self.client_secret, "code": code, "redirect_uri": self.redirect_uri } ) data = response.json() if data.get("ok"): return { "access_token": data["access_token"], "bot_user_id": data["bot_user_id"], "team_id": data["team"]["id"], "team_name": data["team"]["name"], "scope": data["scope"], "app_id": data["app_id"] } else: logger.error(f"Slack OAuth error: {data.get('error')}") return None except Exception as e: logger.error(f"Error exchanging code for token: {e}") return None async def _get_workspace_info(self, access_token: str) -> Dict[str, Any]: """Get workspace information.""" try: async with httpx.AsyncClient() as client: response = await client.get( "https://slack.com/api/team.info", headers={"Authorization": f"Bearer {access_token}"} ) data = response.json() return data if data.get("ok") else {} except Exception as e: logger.error(f"Error getting workspace info: {e}") return {} async def _get_bot_info(self, bot_user_id: str, access_token: str) -> Dict[str, Any]: """Get bot user information.""" try: async with httpx.AsyncClient() as client: response = await client.get( f"https://slack.com/api/users.info?user={bot_user_id}", headers={"Authorization": f"Bearer {access_token}"} ) data = response.json() return data.get("user", {}) if data.get("ok") else {} except Exception as e: logger.error(f"Error getting bot info: {e}") return {} async def _create_agent_trigger( self, agent_id: str, user_id: str, workspace_info: Dict[str, Any], bot_info: Dict[str, Any], oauth_data: Dict[str, Any] ) -> Dict[str, Any]: """Create agent trigger with OAuth data.""" from ..core import TriggerManager trigger_manager = TriggerManager(self.db) await trigger_manager.load_provider_definitions() config = { "access_token": oauth_data["access_token"], "bot_user_id": oauth_data["bot_user_id"], "team_id": oauth_data["team_id"], "team_name": oauth_data["team_name"], "bot_name": bot_info.get("name", "Agent Bot"), "respond_to_mentions": True, "respond_to_direct_messages": True, "allowed_channels": [], "trigger_keywords": [], "oauth_installed": True } trigger_config = await trigger_manager.create_trigger( agent_id=agent_id, provider_id="slack_oauth", name=f"Slack - {oauth_data['team_name']}", description=f"Auto-configured Slack integration for {oauth_data['team_name']} workspace", config=config ) await self._store_oauth_data(trigger_config.trigger_id, oauth_data, workspace_info, bot_info) base_url = os.getenv("WEBHOOK_BASE_URL", "http://localhost:8000") webhook_url = f"{base_url}/api/triggers/{trigger_config.trigger_id}/webhook" return { "trigger_id": trigger_config.trigger_id, "webhook_url": webhook_url } def _create_state_token(self, agent_id: str, user_id: str) -> str: state_data = { "agent_id": agent_id, "user_id": user_id, "nonce": secrets.token_urlsafe(16) } import base64 state_json = json.dumps(state_data) return base64.b64encode(state_json.encode()).decode() async def _verify_state_token(self, state: str) -> Optional[Dict[str, Any]]: """Verify and decode state token.""" try: import base64 state_json = base64.b64decode(state.encode()).decode() return json.loads(state_json) except Exception as e: logger.error(f"Error verifying state token: {e}") return None async def _store_oauth_data( self, trigger_id: str, oauth_data: Dict[str, Any], workspace_info: Dict[str, Any], bot_info: Dict[str, Any] ): """Store OAuth data for the trigger.""" client = await self.db.client await client.table('slack_oauth_installations').insert({ 'trigger_id': trigger_id, 'team_id': oauth_data['team_id'], 'team_name': oauth_data['team_name'], 'access_token': oauth_data['access_token'], 'bot_user_id': oauth_data['bot_user_id'], 'bot_name': bot_info.get('name'), 'app_id': oauth_data['app_id'], 'scope': oauth_data['scope'], 'workspace_info': workspace_info, 'bot_info': bot_info, 'installed_at': 'now()' }).execute()