2025-06-30 18:57:34 +08:00
import os
from typing import Dict , Any
from . base import BaseOAuthProvider , OAuthConfig , OAuthProvider , OAuthTokenResponse
import httpx
class SlackOAuthProvider ( BaseOAuthProvider ) :
""" Slack OAuth provider implementation. """
def __init__ ( self , db_connection ) :
2025-07-01 02:03:46 +08:00
client_id = os . getenv ( " SLACK_CLIENT_ID " )
client_secret = os . getenv ( " SLACK_CLIENT_SECRET " )
if not client_id :
raise ValueError ( " SLACK_CLIENT_ID environment variable is required for Slack OAuth integration. Get this from your Slack app ' s ' Basic Information ' page. " )
if not client_secret :
raise ValueError ( " SLACK_CLIENT_SECRET environment variable is required for Slack OAuth integration. Get this from your Slack app ' s ' Basic Information ' page. " )
2025-06-30 18:57:34 +08:00
config = OAuthConfig (
provider = OAuthProvider . SLACK ,
2025-07-01 02:03:46 +08:00
client_id = client_id ,
client_secret = client_secret ,
2025-06-30 18:57:34 +08:00
authorization_url = " https://slack.com/oauth/v2/authorize " ,
token_url = " https://slack.com/api/oauth.v2.access " ,
scopes = [ " app_mentions:read " , " channels:read " , " chat:write " , " im:read " , " im:write " , " users:read " ] ,
2025-07-03 12:47:57 +08:00
redirect_uri = os . getenv ( " SLACK_REDIRECT_URI " , " http://localhost:3000/api/integrations/slack/callback " )
2025-06-30 18:57:34 +08:00
)
super ( ) . __init__ ( config , db_connection )
def _is_token_response_valid ( self , data : Dict [ str , Any ] ) - > bool :
return data . get ( " ok " ) is True
async def _get_provider_data ( self , token_response : OAuthTokenResponse ) - > Dict [ str , Any ] :
access_token = token_response . access_token
additional_data = token_response . additional_data
workspace_info = await self . _get_workspace_info ( access_token )
bot_user_id = additional_data . get ( " bot_user_id " )
bot_info = await self . _get_bot_info ( bot_user_id , access_token ) if bot_user_id else { }
return {
" workspace_name " : additional_data . get ( " team " , { } ) . get ( " name " ) ,
" workspace_id " : additional_data . get ( " team " , { } ) . get ( " id " ) ,
" bot_name " : bot_info . get ( " name " , " Agent Bot " ) ,
" bot_user_id " : bot_user_id ,
" app_id " : additional_data . get ( " app_id " ) ,
" workspace_info " : workspace_info ,
2025-07-01 02:03:46 +08:00
" bot_info " : bot_info ,
" access_token " : access_token
2025-06-30 18:57:34 +08:00
}
def _get_trigger_config ( self , token_response : OAuthTokenResponse , provider_data : Dict [ str , Any ] ) - > Dict [ str , Any ] :
return {
" access_token " : token_response . access_token ,
" bot_user_id " : provider_data . get ( " bot_user_id " ) ,
" team_id " : provider_data . get ( " workspace_id " ) ,
" team_name " : provider_data . get ( " workspace_name " ) ,
" bot_name " : provider_data . get ( " bot_name " ) ,
" respond_to_mentions " : True ,
" respond_to_direct_messages " : True ,
" allowed_channels " : [ ] ,
" trigger_keywords " : [ ]
}
async def _get_workspace_info ( self , access_token : str ) - > Dict [ str , Any ] :
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 :
return { }
async def _get_bot_info ( self , bot_user_id : str , access_token : str ) - > Dict [ str , Any ] :
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 :
return { }
2025-07-01 02:03:46 +08:00
async def _setup_slack_webhook ( self , trigger_id : str , access_token : str ) - > bool :
try :
import os
import utils . logger as logger
base_url = os . getenv ( " WEBHOOK_BASE_URL " , " http://localhost:8000 " )
webhook_url = f " { base_url } /api/triggers/slack/webhook "
async with httpx . AsyncClient ( ) as client :
response = await client . post (
" https://slack.com/api/apps.event.authorizations.list " ,
headers = { " Authorization " : f " Bearer { access_token } " }
)
if response . status_code == 200 :
logger . info ( f " Successfully verified Slack app permissions for trigger { trigger_id } " )
logger . info ( f " Universal Slack webhook URL: { webhook_url } " )
return True
else :
logger . warning ( f " Could not verify Slack app permissions: { response . text } " )
return False
except Exception as e :
logger . error ( f " Error setting up Slack webhook: { e } " )
return False
2025-06-30 18:57:34 +08:00
class DiscordOAuthProvider ( BaseOAuthProvider ) :
def __init__ ( self , db_connection ) :
2025-07-01 02:03:46 +08:00
client_id = os . getenv ( " DISCORD_CLIENT_ID " )
client_secret = os . getenv ( " DISCORD_CLIENT_SECRET " )
if not client_id :
raise ValueError ( " DISCORD_CLIENT_ID environment variable is required for Discord OAuth integration " )
if not client_secret :
raise ValueError ( " DISCORD_CLIENT_SECRET environment variable is required for Discord OAuth integration " )
2025-06-30 18:57:34 +08:00
config = OAuthConfig (
provider = OAuthProvider . DISCORD ,
2025-07-01 02:03:46 +08:00
client_id = client_id ,
client_secret = client_secret ,
2025-06-30 18:57:34 +08:00
authorization_url = " https://discord.com/api/oauth2/authorize " ,
token_url = " https://discord.com/api/oauth2/token " ,
scopes = [ " bot " , " applications.commands " ] ,
2025-07-03 12:47:57 +08:00
redirect_uri = os . getenv ( " DISCORD_REDIRECT_URI " , " http://localhost:3000/api/integrations/discord/callback " ) ,
2025-06-30 18:57:34 +08:00
additional_params = { " permissions " : " 2048 " }
)
super ( ) . __init__ ( config , db_connection )
def _is_token_response_valid ( self , data : Dict [ str , Any ] ) - > bool :
return " access_token " in data
async def _get_provider_data ( self , token_response : OAuthTokenResponse ) - > Dict [ str , Any ] :
""" Get Discord-specific data. """
guild_info = token_response . additional_data . get ( " guild " , { } )
return {
" workspace_name " : guild_info . get ( " name " , " Discord Server " ) ,
" workspace_id " : guild_info . get ( " id " ) ,
" bot_name " : " Agent Bot " ,
" permissions " : token_response . additional_data . get ( " permissions " ) ,
" guild_info " : guild_info
}
def _get_trigger_config ( self , token_response : OAuthTokenResponse , provider_data : Dict [ str , Any ] ) - > Dict [ str , Any ] :
""" Generate Discord trigger configuration. """
return {
" access_token " : token_response . access_token ,
" guild_id " : provider_data . get ( " workspace_id " ) ,
" guild_name " : provider_data . get ( " workspace_name " ) ,
" bot_permissions " : provider_data . get ( " permissions " ) ,
" respond_to_mentions " : True ,
" respond_to_direct_messages " : True ,
" allowed_channels " : [ ] ,
" trigger_keywords " : [ ]
}
class TeamsOAuthProvider ( BaseOAuthProvider ) :
""" Microsoft Teams OAuth provider implementation. """
def __init__ ( self , db_connection ) :
2025-07-01 02:03:46 +08:00
client_id = os . getenv ( " TEAMS_CLIENT_ID " )
client_secret = os . getenv ( " TEAMS_CLIENT_SECRET " )
if not client_id :
raise ValueError ( " TEAMS_CLIENT_ID environment variable is required for Teams OAuth integration " )
if not client_secret :
raise ValueError ( " TEAMS_CLIENT_SECRET environment variable is required for Teams OAuth integration " )
2025-06-30 18:57:34 +08:00
config = OAuthConfig (
provider = OAuthProvider . TEAMS ,
2025-07-01 02:03:46 +08:00
client_id = client_id ,
client_secret = client_secret ,
2025-06-30 18:57:34 +08:00
authorization_url = " https://login.microsoftonline.com/common/oauth2/v2.0/authorize " ,
token_url = " https://login.microsoftonline.com/common/oauth2/v2.0/token " ,
scopes = [ " https://graph.microsoft.com/ChannelMessage.Read.All " , " https://graph.microsoft.com/ChannelMessage.Send " ] ,
2025-07-03 12:47:57 +08:00
redirect_uri = os . getenv ( " TEAMS_REDIRECT_URI " , " http://localhost:3000/api/integrations/teams/callback " )
2025-06-30 18:57:34 +08:00
)
super ( ) . __init__ ( config , db_connection )
def _is_token_response_valid ( self , data : Dict [ str , Any ] ) - > bool :
return " access_token " in data
async def _get_provider_data ( self , token_response : OAuthTokenResponse ) - > Dict [ str , Any ] :
""" Get Teams-specific data. """
user_info = await self . _get_user_info ( token_response . access_token )
return {
" workspace_name " : user_info . get ( " organization " , " Teams Organization " ) ,
" workspace_id " : user_info . get ( " tenantId " ) ,
" bot_name " : " Agent Bot " ,
" user_info " : user_info
}
def _get_trigger_config ( self , token_response : OAuthTokenResponse , provider_data : Dict [ str , Any ] ) - > Dict [ str , Any ] :
""" Generate Teams trigger configuration. """
return {
" access_token " : token_response . access_token ,
" tenant_id " : provider_data . get ( " workspace_id " ) ,
" organization " : provider_data . get ( " workspace_name " ) ,
" respond_to_mentions " : True ,
" respond_to_direct_messages " : True ,
" allowed_teams " : [ ] ,
" trigger_keywords " : [ ]
}
async def _get_user_info ( self , access_token : str ) - > Dict [ str , Any ] :
""" Get Teams user information. """
try :
async with httpx . AsyncClient ( ) as client :
response = await client . get (
" https://graph.microsoft.com/v1.0/me " ,
headers = { " Authorization " : f " Bearer { access_token } " }
)
return response . json ( ) if response . status_code == 200 else { }
except Exception :
return { }
OAUTH_PROVIDERS = {
OAuthProvider . SLACK : SlackOAuthProvider ,
OAuthProvider . DISCORD : DiscordOAuthProvider ,
OAuthProvider . TEAMS : TeamsOAuthProvider ,
}
def get_oauth_provider ( provider : OAuthProvider , db_connection ) :
""" Factory function to get OAuth provider instance. """
provider_class = OAUTH_PROVIDERS . get ( provider )
if not provider_class :
raise ValueError ( f " Unsupported OAuth provider: { provider } " )
return provider_class ( db_connection )