diff --git a/surfsense_backend/app/connectors/airtable_connector.py b/surfsense_backend/app/connectors/airtable_connector.py index 840b2276c..ecbba7a19 100644 --- a/surfsense_backend/app/connectors/airtable_connector.py +++ b/surfsense_backend/app/connectors/airtable_connector.py @@ -382,3 +382,46 @@ class AirtableConnector: markdown_parts.append("") return "\n".join(markdown_parts) + + +# --- OAuth User Info --- + +AIRTABLE_WHOAMI_URL = "https://api.airtable.com/v0/meta/whoami" + + +async def fetch_airtable_user_email(access_token: str) -> str | None: + """ + Fetch user email from Airtable whoami API. + + Args: + access_token: The Airtable OAuth access token + + Returns: + User's email address or None if fetch fails + """ + import httpx + import logging + + logger = logging.getLogger(__name__) + + try: + async with httpx.AsyncClient() as client: + response = await client.get( + AIRTABLE_WHOAMI_URL, + headers={"Authorization": f"Bearer {access_token}"}, + timeout=10.0, + ) + + if response.status_code == 200: + data = response.json() + email = data.get("email") + if email: + logger.debug(f"Fetched Airtable user email: {email}") + return email + + logger.warning(f"Failed to fetch Airtable user info: {response.status_code}") + return None + + except Exception as e: + logger.warning(f"Error fetching Airtable user email: {e!s}") + return None diff --git a/surfsense_backend/app/routes/airtable_add_connector_route.py b/surfsense_backend/app/routes/airtable_add_connector_route.py index 7e3358c6f..9632c9308 100644 --- a/surfsense_backend/app/routes/airtable_add_connector_route.py +++ b/surfsense_backend/app/routes/airtable_add_connector_route.py @@ -20,10 +20,11 @@ from app.db import ( User, get_async_session, ) +from app.connectors.airtable_connector import fetch_airtable_user_email from app.schemas.airtable_auth_credentials import AirtableAuthCredentialsBase from app.users import current_active_user +from app.utils.connector_naming import generate_unique_connector_name from app.utils.oauth_security import OAuthStateManager, TokenEncryption -from app.utils.connector_naming import generate_unique_connector_name, extract_identifier_from_credentials logger = logging.getLogger(__name__) @@ -276,6 +277,10 @@ async def airtable_callback( status_code=400, detail="No access token received from Airtable" ) + # Fetch user email before encrypting credentials + user_email = await fetch_airtable_user_email(access_token) + + # Calculate expiration time (UTC, tz-aware) expires_at = None if token_json.get("expires_in"): @@ -298,13 +303,13 @@ async def airtable_callback( credentials_dict = credentials.to_dict() credentials_dict["_token_encrypted"] = True - # Extract unique identifier from connector credentials - connector_identifier = extract_identifier_from_credentials( - SearchSourceConnectorType.AIRTABLE_CONNECTOR, credentials_dict - ) - # Generate a unique, user-friendly connector name from credentials/account info - connector_name = generate_unique_connector_name( - SearchSourceConnectorType.AIRTABLE_CONNECTOR, connector_identifier + # Generate a unique, user-friendly connector name + connector_name = await generate_unique_connector_name( + session, + SearchSourceConnectorType.AIRTABLE_CONNECTOR, + space_id, + user_id, + user_email, ) # Create new connector new_connector = SearchSourceConnector(