fix auth bypass on picker endpoint, async safety, and picker error handling

- Add check_permission to drive-picker-token endpoint (IDOR fix)
- Use get_composio_service singleton + asyncio.to_thread to avoid blocking the event loop
- Sanitize error detail in 500 response to prevent internal info leakage
- Dispose picker on unmount to prevent orphaned overlay
- Surface error state on Google Picker Action.ERROR instead of silently closing
This commit is contained in:
CREDO23 2026-03-10 23:21:35 +02:00
parent 3bda6c1679
commit cf8f70da2b
2 changed files with 33 additions and 12 deletions

View file

@ -52,7 +52,9 @@ from app.schemas import (
SearchSourceConnectorRead, SearchSourceConnectorRead,
SearchSourceConnectorUpdate, SearchSourceConnectorUpdate,
) )
from app.services.composio_service import ComposioService import asyncio
from app.services.composio_service import ComposioService, get_composio_service
from app.services.notification_service import NotificationService from app.services.notification_service import NotificationService
from app.tasks.connector_indexers import ( from app.tasks.connector_indexers import (
index_airtable_records, index_airtable_records,
@ -3080,6 +3082,14 @@ async def get_drive_picker_token(
if not connector: if not connector:
raise HTTPException(status_code=404, detail="Connector not found") raise HTTPException(status_code=404, detail="Connector not found")
await check_permission(
session,
user,
connector.search_space_id,
Permission.CONNECTORS_READ.value,
"You don't have permission to access this connector",
)
if connector.connector_type not in DRIVE_CONNECTOR_TYPES: if connector.connector_type not in DRIVE_CONNECTOR_TYPES:
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
@ -3113,8 +3123,8 @@ async def get_drive_picker_token(
status_code=400, status_code=400,
detail="Composio connected account not found. Please reconnect.", detail="Composio connected account not found. Please reconnect.",
) )
service = ComposioService() service = get_composio_service()
access_token = service.get_access_token(composio_account_id) access_token = await asyncio.to_thread(service.get_access_token, composio_account_id)
return { return {
"access_token": access_token, "access_token": access_token,
"client_id": config.GOOGLE_OAUTH_CLIENT_ID, "client_id": config.GOOGLE_OAUTH_CLIENT_ID,
@ -3127,5 +3137,5 @@ async def get_drive_picker_token(
logger.error(f"Failed to get Drive picker token: {e!s}", exc_info=True) logger.error(f"Failed to get Drive picker token: {e!s}", exc_info=True)
raise HTTPException( raise HTTPException(
status_code=500, status_code=500,
detail=f"Failed to retrieve access token: {e!s}", detail="Failed to retrieve access token. Check server logs for details.",
) from e ) from e

View file

@ -87,7 +87,14 @@ export function useGooglePicker({ connectorId, onPicked }: UseGooglePickerOption
} }
}; };
window.addEventListener("keydown", onEscape); window.addEventListener("keydown", onEscape);
return () => window.removeEventListener("keydown", onEscape); return () => {
window.removeEventListener("keydown", onEscape);
if (pickerRef.current) {
pickerRef.current.dispose();
pickerRef.current = null;
}
openingRef.current = false;
};
}, [closePicker]); }, [closePicker]);
const openPicker = useCallback(async () => { const openPicker = useCallback(async () => {
@ -147,6 +154,10 @@ export function useGooglePicker({ connectorId, onPicked }: UseGooglePickerOption
} }
} }
if (action === google.picker.Action.ERROR) {
setError("Google Drive encountered an error. Please try again.");
}
if ( if (
action === google.picker.Action.PICKED || action === google.picker.Action.PICKED ||
action === google.picker.Action.CANCEL || action === google.picker.Action.CANCEL ||