diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index 6ddf18ebb..2d6c27e26 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -141,10 +141,13 @@ NOTION_CLIENT_ID=your_notion_client_id_here NOTION_CLIENT_SECRET=your_notion_client_secret_here NOTION_REDIRECT_URI=http://localhost:8000/api/v1/auth/notion/connector/callback -# Slack OAuth Configuration +# Slack OAuth / Gateway Configuration +# The Slack connector and Slack gateway can use the same Slack app client ID/secret. SLACK_CLIENT_ID=your_slack_client_id_here SLACK_CLIENT_SECRET=your_slack_client_secret_here SLACK_REDIRECT_URI=http://localhost:8000/api/v1/auth/slack/connector/callback +GATEWAY_SLACK_SIGNING_SECRET=your_slack_signing_secret_here +GATEWAY_SLACK_REDIRECT_URI=http://localhost:8000/api/v1/gateway/slack/callback # Microsoft OAuth (Teams & OneDrive) MICROSOFT_CLIENT_ID=your_microsoft_client_id_here diff --git a/surfsense_backend/app/config/__init__.py b/surfsense_backend/app/config/__init__.py index c8ffa802e..fdf8834c1 100644 --- a/surfsense_backend/app/config/__init__.py +++ b/surfsense_backend/app/config/__init__.py @@ -570,6 +570,10 @@ class Config: raise ValueError( "GATEWAY_WHATSAPP_INTAKE_MODE must be one of: cloud, baileys, disabled" ) + GATEWAY_SLACK_CLIENT_ID = os.getenv("SLACK_CLIENT_ID") + GATEWAY_SLACK_CLIENT_SECRET = os.getenv("SLACK_CLIENT_SECRET") + GATEWAY_SLACK_SIGNING_SECRET = os.getenv("GATEWAY_SLACK_SIGNING_SECRET") + GATEWAY_SLACK_REDIRECT_URI = os.getenv("GATEWAY_SLACK_REDIRECT_URI") # Stripe checkout for pay-as-you-go page packs STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY") diff --git a/surfsense_backend/app/gateway/accounts.py b/surfsense_backend/app/gateway/accounts.py index 7379336a7..5daf75c69 100644 --- a/surfsense_backend/app/gateway/accounts.py +++ b/surfsense_backend/app/gateway/accounts.py @@ -2,6 +2,8 @@ from __future__ import annotations +import json + from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -25,6 +27,19 @@ def account_token(account: ExternalChatAccount) -> str | None: ) +def slack_account_credentials(account: ExternalChatAccount) -> dict: + """Decrypt Slack gateway credentials stored as encrypted JSON.""" + if not account.encrypted_credentials: + return {} + raw = TokenEncryption(config.SECRET_KEY or "").decrypt_token(account.encrypted_credentials) + try: + data = json.loads(raw) + except json.JSONDecodeError: + # Backward-compatible fallback if a token string was stored directly. + return {"bot_token": raw} + return data if isinstance(data, dict) else {} + + async def get_or_create_system_telegram_account( session: AsyncSession, ) -> ExternalChatAccount: @@ -78,3 +93,18 @@ async def get_or_create_system_whatsapp_account( await session.flush() return account + +async def get_slack_account_by_team( + session: AsyncSession, + *, + team_id: str, +) -> ExternalChatAccount | None: + result = await session.execute( + select(ExternalChatAccount).where( + ExternalChatAccount.platform == ExternalChatPlatform.SLACK, + ExternalChatAccount.is_system_account.is_(True), + ExternalChatAccount.cursor_state["team_id"].astext == team_id, + ) + ) + return result.scalars().first() +