diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index 0af368081..413be03c4 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -50,6 +50,7 @@ REGISTRATION_ENABLED=TRUE or FALSE # For Google Auth Only GOOGLE_OAUTH_CLIENT_ID=924507538m GOOGLE_OAUTH_CLIENT_SECRET=GOCSV +GOOGLE_PICKER_API_KEY=your-google-picker-api-key # Google Connector Specific Configurations GOOGLE_CALENDAR_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/calendar/connector/callback diff --git a/surfsense_backend/app/config/__init__.py b/surfsense_backend/app/config/__init__.py index 68c65a818..1ddc54e2a 100644 --- a/surfsense_backend/app/config/__init__.py +++ b/surfsense_backend/app/config/__init__.py @@ -235,6 +235,7 @@ class Config: # Google OAuth GOOGLE_OAUTH_CLIENT_ID = os.getenv("GOOGLE_OAUTH_CLIENT_ID") GOOGLE_OAUTH_CLIENT_SECRET = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET") + GOOGLE_PICKER_API_KEY = os.getenv("GOOGLE_PICKER_API_KEY") # Google Calendar redirect URI GOOGLE_CALENDAR_REDIRECT_URI = os.getenv("GOOGLE_CALENDAR_REDIRECT_URI") diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py index e808635e6..64aeab431 100644 --- a/surfsense_backend/app/routes/search_source_connectors_routes.py +++ b/surfsense_backend/app/routes/search_source_connectors_routes.py @@ -3054,3 +3054,78 @@ async def test_mcp_server_connection( "message": f"Failed to test connection: {e!s}", "tools": [], } + + +# --------------------------------------------------------------------------- +# Google Picker token endpoint (unified for native & Composio Drive) +# --------------------------------------------------------------------------- + +DRIVE_CONNECTOR_TYPES = { + SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR, + SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR, +} + + +@router.get("/connectors/{connector_id}/drive-picker-token") +async def get_drive_picker_token( + connector_id: int, + session: AsyncSession = Depends(get_async_session), + user: User = Depends(current_active_user), +): + """Return an OAuth access token + client ID for the Google Picker API.""" + result = await session.execute( + select(SearchSourceConnector).filter(SearchSourceConnector.id == connector_id) + ) + connector = result.scalars().first() + if not connector: + raise HTTPException(status_code=404, detail="Connector not found") + + if connector.connector_type not in DRIVE_CONNECTOR_TYPES: + raise HTTPException( + status_code=400, + detail="This endpoint is only for Google Drive connectors", + ) + + picker_api_key = config.GOOGLE_PICKER_API_KEY + if not picker_api_key: + raise HTTPException( + status_code=500, + detail="GOOGLE_PICKER_API_KEY is not configured on the server", + ) + + try: + if connector.connector_type == SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR: + from app.connectors.google_drive.credentials import get_valid_credentials + + credentials = await get_valid_credentials(session, connector_id) + return { + "access_token": credentials.token, + "client_id": config.GOOGLE_OAUTH_CLIENT_ID, + "picker_api_key": picker_api_key, + } + + # Composio path + composio_account_id = (connector.config or {}).get( + "composio_connected_account_id" + ) + if not composio_account_id: + raise HTTPException( + status_code=400, + detail="Composio connected account not found. Please reconnect.", + ) + service = ComposioService() + access_token = service.get_access_token(composio_account_id) + return { + "access_token": access_token, + "client_id": config.GOOGLE_OAUTH_CLIENT_ID, + "picker_api_key": picker_api_key, + } + + except HTTPException: + raise + except Exception as e: + logger.error(f"Failed to get Drive picker token: {e!s}", exc_info=True) + raise HTTPException( + status_code=500, + detail=f"Failed to retrieve access token: {e!s}", + ) from e diff --git a/surfsense_backend/app/services/composio_service.py b/surfsense_backend/app/services/composio_service.py index 763347d5a..3930d38ad 100644 --- a/surfsense_backend/app/services/composio_service.py +++ b/surfsense_backend/app/services/composio_service.py @@ -247,6 +247,19 @@ class ComposioService: ) return False + def get_access_token(self, connected_account_id: str) -> str: + """Retrieve the raw OAuth access token for a Composio connected account.""" + account = self.client.connected_accounts.get(nanoid=connected_account_id) + token = getattr(getattr(account, "state", None), "val", None) + if token is None: + raise ValueError( + f"No state.val on connected account {connected_account_id}" + ) + access_token = getattr(token, "access_token", None) + if not access_token: + raise ValueError(f"No access_token in state.val for {connected_account_id}") + return access_token + async def execute_tool( self, connected_account_id: str,