diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index 83656a910..2cacedc21 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -54,6 +54,11 @@ NOTION_CLIENT_ID=your_notion_client_id NOTION_CLIENT_SECRET=your_notion_client_secret NOTION_REDIRECT_URI=http://localhost:8000/api/v1/auth/notion/connector/callback +# OAuth for Slack connector +SLACK_CLIENT_ID=1234567890.1234567890123 +SLACK_CLIENT_SECRET=abcdefghijklmnopqrstuvwxyz1234567890 +SLACK_REDIRECT_URI=http://localhost:8000/api/v1/auth/slack/connector/callback + # Embedding Model # Examples: # # Get sentence transformers embeddings diff --git a/surfsense_backend/app/config/__init__.py b/surfsense_backend/app/config/__init__.py index 7c7703470..f69d1c1a3 100644 --- a/surfsense_backend/app/config/__init__.py +++ b/surfsense_backend/app/config/__init__.py @@ -100,6 +100,11 @@ class Config: LINEAR_CLIENT_SECRET = os.getenv("LINEAR_CLIENT_SECRET") LINEAR_REDIRECT_URI = os.getenv("LINEAR_REDIRECT_URI") + # Slack OAuth + SLACK_CLIENT_ID = os.getenv("SLACK_CLIENT_ID") + SLACK_CLIENT_SECRET = os.getenv("SLACK_CLIENT_SECRET") + SLACK_REDIRECT_URI = os.getenv("SLACK_REDIRECT_URI") + # LLM instances are now managed per-user through the LLMConfig system # Legacy environment variables removed in favor of user-specific configurations diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index 1d1fa39ad..05020deff 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -26,6 +26,7 @@ from .podcasts_routes import router as podcasts_router from .rbac_routes import router as rbac_router from .search_source_connectors_routes import router as search_source_connectors_router from .search_spaces_routes import router as search_spaces_router +from .slack_add_connector_route import router as slack_add_connector_router router = APIRouter() @@ -44,6 +45,7 @@ router.include_router(airtable_add_connector_router) router.include_router(linear_add_connector_router) router.include_router(luma_add_connector_router) router.include_router(notion_add_connector_router) +router.include_router(slack_add_connector_router) router.include_router(new_llm_config_router) # LLM configs with prompt configuration router.include_router(logs_router) router.include_router(circleback_webhook_router) # Circleback meeting webhooks diff --git a/surfsense_backend/app/routes/slack_add_connector_route.py b/surfsense_backend/app/routes/slack_add_connector_route.py new file mode 100644 index 000000000..1bbb4f5f1 --- /dev/null +++ b/surfsense_backend/app/routes/slack_add_connector_route.py @@ -0,0 +1,336 @@ +""" +Slack Connector OAuth Routes. + +Handles OAuth 2.0 authentication flow for Slack connector. +""" + +import logging +from datetime import UTC, datetime, timedelta +from uuid import UUID + +import httpx +from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi.responses import RedirectResponse +from pydantic import ValidationError +from sqlalchemy.exc import IntegrityError +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select + +from app.config import config +from app.db import ( + SearchSourceConnector, + SearchSourceConnectorType, + User, + get_async_session, +) +from app.users import current_active_user +from app.utils.oauth_security import OAuthStateManager, TokenEncryption + +logger = logging.getLogger(__name__) + +router = APIRouter() + +# Slack OAuth endpoints +AUTHORIZATION_URL = "https://slack.com/oauth/v2/authorize" +TOKEN_URL = "https://slack.com/api/oauth.v2.access" + +# OAuth scopes for Slack (Bot Token) +SCOPES = [ + "channels:history", # Read messages in public channels + "channels:read", # View basic information about public channels + "groups:history", # Read messages in private channels + "groups:read", # View basic information about private channels + "im:history", # Read messages in direct messages + "mpim:history", # Read messages in group direct messages + "users:read", # Read user information +] + +# Initialize security utilities +_state_manager = None +_token_encryption = None + + +def get_state_manager() -> OAuthStateManager: + """Get or create OAuth state manager instance.""" + global _state_manager + if _state_manager is None: + if not config.SECRET_KEY: + raise ValueError("SECRET_KEY must be set for OAuth security") + _state_manager = OAuthStateManager(config.SECRET_KEY) + return _state_manager + + +def get_token_encryption() -> TokenEncryption: + """Get or create token encryption instance.""" + global _token_encryption + if _token_encryption is None: + if not config.SECRET_KEY: + raise ValueError("SECRET_KEY must be set for token encryption") + _token_encryption = TokenEncryption(config.SECRET_KEY) + return _token_encryption + + +@router.get("/auth/slack/connector/add") +async def connect_slack(space_id: int, user: User = Depends(current_active_user)): + """ + Initiate Slack OAuth flow. + + Args: + space_id: The search space ID + user: Current authenticated user + + Returns: + Authorization URL for redirect + """ + try: + if not space_id: + raise HTTPException(status_code=400, detail="space_id is required") + + if not config.SLACK_CLIENT_ID: + raise HTTPException(status_code=500, detail="Slack OAuth not configured.") + + if not config.SECRET_KEY: + raise HTTPException( + status_code=500, detail="SECRET_KEY not configured for OAuth security." + ) + + # Generate secure state parameter with HMAC signature + state_manager = get_state_manager() + state_encoded = state_manager.generate_secure_state(space_id, user.id) + + # Build authorization URL + from urllib.parse import urlencode + + auth_params = { + "client_id": config.SLACK_CLIENT_ID, + "scope": ",".join(SCOPES), + "redirect_uri": config.SLACK_REDIRECT_URI, + "state": state_encoded, + } + + auth_url = f"{AUTHORIZATION_URL}?{urlencode(auth_params)}" + + logger.info(f"Generated Slack OAuth URL for user {user.id}, space {space_id}") + return {"auth_url": auth_url} + + except Exception as e: + logger.error(f"Failed to initiate Slack OAuth: {e!s}", exc_info=True) + raise HTTPException( + status_code=500, detail=f"Failed to initiate Slack OAuth: {e!s}" + ) from e + + +@router.get("/auth/slack/connector/callback") +async def slack_callback( + request: Request, + code: str | None = None, + error: str | None = None, + state: str | None = None, + session: AsyncSession = Depends(get_async_session), +): + """ + Handle Slack OAuth callback. + + Args: + request: FastAPI request object + code: Authorization code from Slack (if user granted access) + error: Error code from Slack (if user denied access or error occurred) + state: State parameter containing user/space info + session: Database session + + Returns: + Redirect response to frontend + """ + try: + # Handle OAuth errors (e.g., user denied access) + if error: + logger.warning(f"Slack OAuth error: {error}") + # Try to decode state to get space_id for redirect, but don't fail if it's invalid + space_id = None + if state: + try: + state_manager = get_state_manager() + data = state_manager.validate_state(state) + space_id = data.get("space_id") + except Exception: + # If state is invalid, we'll redirect without space_id + logger.warning("Failed to validate state in error handler") + + # Redirect to frontend with error parameter + if space_id: + return RedirectResponse( + url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=slack_oauth_denied" + ) + else: + return RedirectResponse( + url=f"{config.NEXT_FRONTEND_URL}/dashboard?error=slack_oauth_denied" + ) + + # Validate required parameters for successful flow + if not code: + raise HTTPException(status_code=400, detail="Missing authorization code") + if not state: + raise HTTPException(status_code=400, detail="Missing state parameter") + + # Validate and decode state with signature verification + state_manager = get_state_manager() + try: + data = state_manager.validate_state(state) + except HTTPException: + raise + except Exception as e: + raise HTTPException( + status_code=400, detail=f"Invalid state parameter: {e!s}" + ) from e + + user_id = UUID(data["user_id"]) + space_id = data["space_id"] + + # Validate redirect URI (security: ensure it matches configured value) + if not config.SLACK_REDIRECT_URI: + raise HTTPException( + status_code=500, detail="SLACK_REDIRECT_URI not configured" + ) + + # Exchange authorization code for access token + token_data = { + "client_id": config.SLACK_CLIENT_ID, + "client_secret": config.SLACK_CLIENT_SECRET, + "code": code, + "redirect_uri": config.SLACK_REDIRECT_URI, + } + + async with httpx.AsyncClient() as client: + token_response = await client.post( + TOKEN_URL, + data=token_data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=30.0, + ) + + if token_response.status_code != 200: + error_detail = token_response.text + try: + error_json = token_response.json() + error_detail = error_json.get("error", error_detail) + except Exception: + pass + raise HTTPException( + status_code=400, detail=f"Token exchange failed: {error_detail}" + ) + + token_json = token_response.json() + + # Slack OAuth v2 returns success status in the JSON + if not token_json.get("ok", False): + error_msg = token_json.get("error", "Unknown error") + raise HTTPException( + status_code=400, detail=f"Slack OAuth error: {error_msg}" + ) + + # Extract bot token from Slack response + # Slack OAuth v2 returns: { "ok": true, "access_token": "...", "bot": { "bot_user_id": "...", "bot_access_token": "xoxb-..." }, ... } + bot_token = None + if token_json.get("bot") and token_json["bot"].get("bot_access_token"): + bot_token = token_json["bot"]["bot_access_token"] + elif token_json.get("access_token"): + # Fallback to access_token if bot token not available + bot_token = token_json["access_token"] + else: + raise HTTPException( + status_code=400, detail="No bot token received from Slack" + ) + + # Encrypt sensitive tokens before storing + token_encryption = get_token_encryption() + + # Calculate expiration time (UTC, tz-aware) + # Slack tokens don't expire by default, but we'll store expiration info if provided + expires_at = None + if token_json.get("expires_in"): + now_utc = datetime.now(UTC) + expires_at = now_utc + timedelta(seconds=int(token_json["expires_in"])) + + # Store the encrypted bot token in connector config + connector_config = { + "bot_token": token_encryption.encrypt_token(bot_token), + "bot_user_id": token_json.get("bot", {}).get("bot_user_id"), + "team_id": token_json.get("team", {}).get("id"), + "team_name": token_json.get("team", {}).get("name"), + "token_type": token_json.get("token_type", "Bearer"), + "expires_in": token_json.get("expires_in"), + "expires_at": expires_at.isoformat() if expires_at else None, + "scope": token_json.get("scope"), + # Mark that tokens are encrypted for backward compatibility + "_token_encrypted": True, + } + + # Check if connector already exists for this search space and user + existing_connector_result = await session.execute( + select(SearchSourceConnector).filter( + SearchSourceConnector.search_space_id == space_id, + SearchSourceConnector.user_id == user_id, + SearchSourceConnector.connector_type + == SearchSourceConnectorType.SLACK_CONNECTOR, + ) + ) + existing_connector = existing_connector_result.scalars().first() + + if existing_connector: + # Update existing connector + existing_connector.config = connector_config + existing_connector.name = "Slack Connector" + existing_connector.is_indexable = True + logger.info( + f"Updated existing Slack connector for user {user_id} in space {space_id}" + ) + else: + # Create new connector + new_connector = SearchSourceConnector( + name="Slack Connector", + connector_type=SearchSourceConnectorType.SLACK_CONNECTOR, + is_indexable=True, + config=connector_config, + search_space_id=space_id, + user_id=user_id, + ) + session.add(new_connector) + logger.info( + f"Created new Slack connector for user {user_id} in space {space_id}" + ) + + try: + await session.commit() + logger.info(f"Successfully saved Slack connector for user {user_id}") + + # Redirect to the frontend with success params + return RedirectResponse( + url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=slack-connector" + ) + + except ValidationError as e: + await session.rollback() + raise HTTPException( + status_code=422, detail=f"Validation error: {e!s}" + ) from e + except IntegrityError as e: + await session.rollback() + raise HTTPException( + status_code=409, + detail=f"Integrity error: A connector with this type already exists. {e!s}", + ) from e + except Exception as e: + logger.error(f"Failed to create search source connector: {e!s}") + await session.rollback() + raise HTTPException( + status_code=500, + detail=f"Failed to create search source connector: {e!s}", + ) from e + + except HTTPException: + raise + except Exception as e: + logger.error(f"Failed to complete Slack OAuth: {e!s}", exc_info=True) + raise HTTPException( + status_code=500, detail=f"Failed to complete Slack OAuth: {e!s}" + ) from e diff --git a/surfsense_backend/app/schemas/search_source_connector.py b/surfsense_backend/app/schemas/search_source_connector.py index b0d8ebc3a..dbe4dce1f 100644 --- a/surfsense_backend/app/schemas/search_source_connector.py +++ b/surfsense_backend/app/schemas/search_source_connector.py @@ -31,7 +31,7 @@ class SearchSourceConnectorBase(BaseModel): @model_validator(mode="after") def validate_periodic_indexing(self): """Validate that periodic indexing configuration is consistent. - + Supported frequencies: Any positive integer (in minutes). Common values: 5, 15, 60 (1 hour), 360 (6 hours), 720 (12 hours), 1440 (daily), etc. The schedule checker will handle any frequency >= 1 minute. diff --git a/surfsense_backend/app/tasks/connector_indexers/slack_indexer.py b/surfsense_backend/app/tasks/connector_indexers/slack_indexer.py index 5119aba2e..4c4191a4e 100644 --- a/surfsense_backend/app/tasks/connector_indexers/slack_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/slack_indexer.py @@ -17,6 +17,7 @@ from app.utils.document_converters import ( generate_content_hash, generate_unique_identifier_hash, ) +from app.utils.oauth_security import TokenEncryption from .base import ( build_document_metadata_markdown, @@ -93,7 +94,10 @@ async def index_slack_messages( ) # Get the Slack token from the connector config - slack_token = connector.config.get("SLACK_BOT_TOKEN") + # Support both new OAuth format (bot_token) and old API format (SLACK_BOT_TOKEN) + config_data = connector.config.copy() + slack_token = config_data.get("bot_token") or config_data.get("SLACK_BOT_TOKEN") + if not slack_token: await task_logger.log_task_failure( log_entry, @@ -103,6 +107,22 @@ async def index_slack_messages( ) return 0, "Slack token not found in connector config" + # Decrypt token if it's encrypted (OAuth format) + token_encrypted = config_data.get("_token_encrypted", False) + if token_encrypted and config.SECRET_KEY: + try: + token_encryption = TokenEncryption(config.SECRET_KEY) + slack_token = token_encryption.decrypt_token(slack_token) + logger.info(f"Decrypted Slack bot token for connector {connector_id}") + except Exception as e: + await task_logger.log_task_failure( + log_entry, + f"Failed to decrypt Slack token for connector {connector_id}: {e!s}", + "Token decryption failed", + {"error_type": "TokenDecryptionError"}, + ) + return 0, f"Failed to decrypt Slack token: {e!s}" + # Initialize Slack client await task_logger.log_task_progress( log_entry, @@ -112,6 +132,12 @@ async def index_slack_messages( slack_client = SlackHistory(token=slack_token) + # Handle 'undefined' string from frontend (treat as None) + if start_date == "undefined" or start_date == "": + start_date = None + if end_date == "undefined" or end_date == "": + end_date = None + # Calculate date range await task_logger.log_task_progress( log_entry, diff --git a/surfsense_backend/app/utils/validators.py b/surfsense_backend/app/utils/validators.py index d6622bafd..8db6ed4a3 100644 --- a/surfsense_backend/app/utils/validators.py +++ b/surfsense_backend/app/utils/validators.py @@ -513,7 +513,22 @@ def validate_connector_config( ], "validators": {}, }, - "SLACK_CONNECTOR": {"required": ["SLACK_BOT_TOKEN"], "validators": {}}, + # "SLACK_CONNECTOR": { + # "required": [], # OAuth uses bot_token (encrypted), legacy uses SLACK_BOT_TOKEN + # "optional": [ + # "bot_token", + # "SLACK_BOT_TOKEN", + # "bot_user_id", + # "team_id", + # "team_name", + # "token_type", + # "expires_in", + # "expires_at", + # "scope", + # "_token_encrypted", + # ], + # "validators": {}, + # }, "GITHUB_CONNECTOR": { "required": ["GITHUB_PAT", "repo_full_names"], "validators": { diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/slack-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/slack-connect-form.tsx deleted file mode 100644 index 3952144e6..000000000 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/slack-connect-form.tsx +++ /dev/null @@ -1,429 +0,0 @@ -"use client"; - -import { zodResolver } from "@hookform/resolvers/zod"; -import { Info } from "lucide-react"; -import type { FC } from "react"; -import { useRef, useState } from "react"; -import { useForm } from "react-hook-form"; -import * as z from "zod"; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Switch } from "@/components/ui/switch"; -import { EnumConnectorName } from "@/contracts/enums/connector"; -import { DateRangeSelector } from "../../components/date-range-selector"; -import { getConnectorBenefits } from "../connector-benefits"; -import type { ConnectFormProps } from "../index"; - -const slackConnectorFormSchema = z.object({ - name: z.string().min(3, { - message: "Connector name must be at least 3 characters.", - }), - bot_token: z.string().min(10, { - message: "Slack Bot Token is required and must be valid.", - }), -}); - -type SlackConnectorFormValues = z.infer; - -export const SlackConnectForm: FC = ({ onSubmit, isSubmitting }) => { - const isSubmittingRef = useRef(false); - const [startDate, setStartDate] = useState(undefined); - const [endDate, setEndDate] = useState(undefined); - const [periodicEnabled, setPeriodicEnabled] = useState(false); - const [frequencyMinutes, setFrequencyMinutes] = useState("1440"); - const form = useForm({ - resolver: zodResolver(slackConnectorFormSchema), - defaultValues: { - name: "Slack Connector", - bot_token: "", - }, - }); - - const handleSubmit = async (values: SlackConnectorFormValues) => { - // Prevent multiple submissions - if (isSubmittingRef.current || isSubmitting) { - return; - } - - isSubmittingRef.current = true; - try { - await onSubmit({ - name: values.name, - connector_type: EnumConnectorName.SLACK_CONNECTOR, - config: { - SLACK_BOT_TOKEN: values.bot_token, - }, - is_indexable: true, - last_indexed_at: null, - periodic_indexing_enabled: periodicEnabled, - indexing_frequency_minutes: periodicEnabled ? parseInt(frequencyMinutes, 10) : null, - next_scheduled_at: null, - startDate, - endDate, - periodicEnabled, - frequencyMinutes, - }); - } finally { - isSubmittingRef.current = false; - } - }; - - return ( -
- - -
- Bot User OAuth Token Required - - You'll need a Slack Bot User OAuth Token to use this connector. You can create a Slack - app and get the token from{" "} - - Slack API Dashboard - - -
-
- -
-
- - ( - - Connector Name - - - - - A friendly name to identify this connector. - - - - )} - /> - - ( - - Slack Bot User OAuth Token - - - - - Your Bot User OAuth Token will be encrypted and stored securely. It typically - starts with "xoxb-". - - - - )} - /> - - {/* Indexing Configuration */} -
-

Indexing Configuration

- - {/* Date Range Selector */} - - - {/* Periodic Sync Config */} -
-
-
-

Enable Periodic Sync

-

- Automatically re-index at regular intervals -

-
- -
- - {periodicEnabled && ( -
-
- - -
-
- )} -
-
- - -
- - {/* What you get section */} - {getConnectorBenefits(EnumConnectorName.SLACK_CONNECTOR) && ( -
-

What you get with Slack integration:

-
    - {getConnectorBenefits(EnumConnectorName.SLACK_CONNECTOR)?.map((benefit) => ( -
  • {benefit}
  • - ))} -
-
- )} - - {/* Documentation Section */} - - - - Documentation - - -
-

How it works

-

- The Slack connector uses the Slack Web API to fetch messages from all accessible - channels that the bot token has access to within a workspace. -

-
    -
  • - For follow up indexing runs, the connector retrieves messages that have been - updated since the last indexing attempt. -
  • -
  • - Indexing is configured to run periodically, so updates should appear in your - search results within minutes. -
  • -
-
- -
-
-

Authorization

- - - - Bot User OAuth Token Required - - - You need to create a Slack app and install it to your workspace to get a Bot - User OAuth Token. The bot needs read access to channels and messages. - - - -
-
-

- Step 1: Create a Slack App -

-
    -
  1. - Go to{" "} - - https://api.slack.com/apps - -
  2. -
  3. - Click Create New App and choose "From scratch" -
  4. -
  5. Enter an app name and select your workspace
  6. -
  7. - Click Create App -
  8. -
-
- -
-

- Step 2: Configure Bot Scopes -

-
    -
  1. - Navigate to OAuth & Permissions in the sidebar -
  2. -
  3. - Under Bot Token Scopes, add the following scopes: -
      -
    • - channels:read - - View basic information about public channels -
    • -
    • - channels:history - - View messages in public channels -
    • -
    • - groups:read - View - basic information about private channels -
    • -
    • - groups:history - - View messages in private channels -
    • -
    • - im:read - View - basic information about direct messages -
    • -
    • - im:history - View - messages in direct messages -
    • -
    -
  4. -
-
- -
-

- Step 3: Install App to Workspace -

-
    -
  1. - Go to Install App in the sidebar -
  2. -
  3. - Click Install to Workspace -
  4. -
  5. - Review the permissions and click Allow -
  6. -
  7. - Copy the Bot User OAuth Token from the "OAuth & - Permissions" page (starts with "xoxb-") -
  8. -
-
-
-
-
- -
-
-

Indexing

-
    -
  1. - Navigate to the Connector Dashboard and select the Slack{" "} - Connector. -
  2. -
  3. - Place the Bot User OAuth Token in the form field. -
  4. -
  5. - Click Connect to establish the connection. -
  6. -
  7. Once connected, your Slack messages will be indexed automatically.
  8. -
- - - - What Gets Indexed - -

The Slack connector indexes the following data:

-
    -
  • Messages from all accessible channels (public and private)
  • -
  • Direct messages (if bot has access)
  • -
  • Message timestamps and metadata
  • -
  • Thread replies and conversations
  • -
-
-
-
-
-
-
-
-
- ); -}; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx index 55169fadd..807d4cb7a 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx @@ -11,7 +11,6 @@ import { JiraConnectForm } from "./components/jira-connect-form"; import { LinkupApiConnectForm } from "./components/linkup-api-connect-form"; import { LumaConnectForm } from "./components/luma-connect-form"; import { SearxngConnectForm } from "./components/searxng-connect-form"; -import { SlackConnectForm } from "./components/slack-connect-form"; import { TavilyApiConnectForm } from "./components/tavily-api-connect-form"; export interface ConnectFormProps { @@ -51,8 +50,6 @@ export function getConnectFormComponent(connectorType: string): ConnectFormCompo return BaiduSearchApiConnectForm; case "ELASTICSEARCH_CONNECTOR": return ElasticsearchConnectForm; - case "SLACK_CONNECTOR": - return SlackConnectForm; case "DISCORD_CONNECTOR": return DiscordConnectForm; case "CONFLUENCE_CONNECTOR": diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/slack-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/slack-config.tsx index 73ae6a4f3..58293c4de 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/slack-config.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/slack-config.tsx @@ -1,84 +1,27 @@ "use client"; -import { KeyRound } from "lucide-react"; +import { Info } from "lucide-react"; import type { FC } from "react"; -import { useEffect, useState } from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; import type { ConnectorConfigProps } from "../index"; export interface SlackConfigProps extends ConnectorConfigProps { onNameChange?: (name: string) => void; } -export const SlackConfig: FC = ({ connector, onConfigChange, onNameChange }) => { - const [botToken, setBotToken] = useState( - (connector.config?.SLACK_BOT_TOKEN as string) || "" - ); - const [name, setName] = useState(connector.name || ""); - - // Update bot token and name when connector changes - useEffect(() => { - const token = (connector.config?.SLACK_BOT_TOKEN as string) || ""; - setBotToken(token); - setName(connector.name || ""); - }, [connector.config, connector.name]); - - const handleBotTokenChange = (value: string) => { - setBotToken(value); - if (onConfigChange) { - onConfigChange({ - ...connector.config, - SLACK_BOT_TOKEN: value, - }); - } - }; - - const handleNameChange = (value: string) => { - setName(value); - if (onNameChange) { - onNameChange(value); - } - }; - +export const SlackConfig: FC = () => { return (
- {/* Connector Name */} -
-
- - handleNameChange(e.target.value)} - placeholder="My Slack Connector" - className="border-slate-400/20 focus-visible:border-slate-400/40" - /> -

- A friendly name to identify this connector. -

+
+
+
-
- - {/* Configuration */} -
-
-

Configuration

-
- -
- - handleBotTokenChange(e.target.value)} - placeholder="Begins with xoxb-..." - className="border-slate-400/20 focus-visible:border-slate-400/40" - /> -

- Update your Bot User OAuth Token if needed. +

+

Add Bot to Channels

+

+ Before indexing, add the SurfSense bot to each channel you want to index. The bot can + only access messages from channels it's been added to. Type{" "} + /invite @SurfSense in + any channel to add it.

diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx index a02ae5088..5437426c8 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx @@ -52,7 +52,6 @@ export const ConnectorConnectView: FC = ({ LINKUP_API: "linkup-api-connect-form", BAIDU_SEARCH_API: "baidu-search-api-connect-form", ELASTICSEARCH_CONNECTOR: "elasticsearch-connect-form", - SLACK_CONNECTOR: "slack-connect-form", DISCORD_CONNECTOR: "discord-connect-form", CONFLUENCE_CONNECTOR: "confluence-connect-form", BOOKSTACK_CONNECTOR: "bookstack-connect-form", diff --git a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts index 0ea263430..111b7485d 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts @@ -44,6 +44,13 @@ export const OAUTH_CONNECTORS = [ connectorType: EnumConnectorName.LINEAR_CONNECTOR, authEndpoint: "/api/v1/auth/linear/connector/add/", }, + { + id: "slack-connector", + title: "Slack", + description: "Search Slack messages", + connectorType: EnumConnectorName.SLACK_CONNECTOR, + authEndpoint: "/api/v1/auth/slack/connector/add/", + }, ] as const; // Content Sources (tools that extract and import content from external sources) @@ -64,12 +71,6 @@ export const CRAWLERS = [ // Non-OAuth Connectors (redirect to old connector config pages) export const OTHER_CONNECTORS = [ - { - id: "slack-connector", - title: "Slack", - description: "Search Slack messages", - connectorType: EnumConnectorName.SLACK_CONNECTOR, - }, { id: "discord-connector", title: "Discord",