From 8f9f66b7f804ff7335e9f4481a3c466e5ec29e84 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Sun, 21 Sep 2025 21:14:03 +0200 Subject: [PATCH] handle token token refreshing when expired --- .../routes/airtable_add_connector_route.py | 75 +++++++++++++++++++ .../connector_indexers/airtable_indexer.py | 6 +- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/surfsense_backend/app/routes/airtable_add_connector_route.py b/surfsense_backend/app/routes/airtable_add_connector_route.py index 2747e3a4f..1820c26d2 100644 --- a/surfsense_backend/app/routes/airtable_add_connector_route.py +++ b/surfsense_backend/app/routes/airtable_add_connector_route.py @@ -280,3 +280,78 @@ async def airtable_callback( raise HTTPException( status_code=500, detail=f"Failed to complete Airtable OAuth: {e!s}" ) from e + + +async def refresh_airtable_token( + session: AsyncSession, connector: SearchSourceConnector +): + """ + Refresh the Airtable access token for a connector. + + Args: + session: Database session + connector: Airtable connector to refresh + + Returns: + Updated connector object + """ + try: + logger.info(f"Refreshing Airtable token for connector {connector.id}") + + credentials = AirtableAuthCredentialsBase.from_dict(connector.config) + auth_header = make_basic_auth_header( + config.AIRTABLE_CLIENT_ID, config.AIRTABLE_CLIENT_SECRET + ) + + # Prepare token refresh data + refresh_data = { + "grant_type": "refresh_token", + "refresh_token": credentials.refresh_token, + "client_id": config.AIRTABLE_CLIENT_ID, + "client_secret": config.AIRTABLE_CLIENT_SECRET, + } + + async with httpx.AsyncClient() as client: + token_response = await client.post( + TOKEN_URL, + data=refresh_data, + headers={ + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": auth_header, + }, + timeout=30.0, + ) + + if token_response.status_code != 200: + raise HTTPException( + status_code=400, detail="Token refresh failed: {token_response.text}" + ) + + token_json = token_response.json() + + # Calculate expiration time (UTC, tz-aware) + 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"])) + + # Update credentials object + credentials.access_token = token_json["access_token"] + credentials.expires_in = token_json.get("expires_in") + credentials.expires_at = expires_at + credentials.scope = token_json.get("scope") + + # Update connector config + connector.config = credentials.to_dict() + await session.commit() + await session.refresh(connector) + + logger.info( + f"Successfully refreshed Airtable token for connector {connector.id}" + ) + + return connector + except Exception as e: + raise HTTPException( + status_code=500, detail=f"Failed to refresh Airtable token: {e!s}" + ) from e diff --git a/surfsense_backend/app/tasks/connector_indexers/airtable_indexer.py b/surfsense_backend/app/tasks/connector_indexers/airtable_indexer.py index 9ba2c2bdc..7ba2f2d44 100644 --- a/surfsense_backend/app/tasks/connector_indexers/airtable_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/airtable_indexer.py @@ -8,6 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.config import config from app.connectors.airtable_connector import AirtableConnector from app.db import Document, DocumentType, SearchSourceConnectorType +from app.routes.airtable_add_connector_route import refresh_airtable_token from app.schemas.airtable_auth_credentials import AirtableAuthCredentialsBase from app.services.llm_service import get_user_long_context_llm from app.services.task_logging_service import TaskLoggingService @@ -102,7 +103,10 @@ async def index_airtable_records( "Credentials expired", {"error_type": "ExpiredCredentials"}, ) - return 0, "Airtable credentials have expired. Please re-authenticate." + + connector = await refresh_airtable_token(session, connector) + + # return 0, "Airtable credentials have expired. Please re-authenticate." # Calculate date range for indexing start_date_str, end_date_str = calculate_date_range(