diff --git a/surfsense_backend/app/connectors/google_calendar_connector.py b/surfsense_backend/app/connectors/google_calendar_connector.py index cc8bc735a..6d389ddd5 100644 --- a/surfsense_backend/app/connectors/google_calendar_connector.py +++ b/surfsense_backend/app/connectors/google_calendar_connector.py @@ -109,14 +109,14 @@ class GoogleCalendarConnector: raise RuntimeError( "GOOGLE_CALENDAR_CONNECTOR connector not found; cannot persist refreshed token." ) - + # Encrypt sensitive credentials before storing from app.config import config from app.utils.oauth_security import TokenEncryption - + creds_dict = json.loads(self._credentials.to_json()) token_encrypted = connector.config.get("_token_encrypted", False) - + if token_encrypted and config.SECRET_KEY: token_encryption = TokenEncryption(config.SECRET_KEY) # Encrypt sensitive fields @@ -125,15 +125,19 @@ class GoogleCalendarConnector: creds_dict["token"] ) if creds_dict.get("refresh_token"): - creds_dict["refresh_token"] = token_encryption.encrypt_token( - creds_dict["refresh_token"] + creds_dict["refresh_token"] = ( + token_encryption.encrypt_token( + creds_dict["refresh_token"] + ) ) if creds_dict.get("client_secret"): - creds_dict["client_secret"] = token_encryption.encrypt_token( - creds_dict["client_secret"] + creds_dict["client_secret"] = ( + token_encryption.encrypt_token( + creds_dict["client_secret"] + ) ) creds_dict["_token_encrypted"] = True - + connector.config = creds_dict flag_modified(connector, "config") await self._session.commit() @@ -209,9 +213,15 @@ class GoogleCalendarConnector: try: # Validate date strings if not start_date or start_date.lower() in ("undefined", "null", "none"): - return [], "Invalid start_date: must be a valid date string in YYYY-MM-DD format" + return ( + [], + "Invalid start_date: must be a valid date string in YYYY-MM-DD format", + ) if not end_date or end_date.lower() in ("undefined", "null", "none"): - return [], "Invalid end_date: must be a valid date string in YYYY-MM-DD format" + return ( + [], + "Invalid end_date: must be a valid date string in YYYY-MM-DD format", + ) service = await self._get_service() diff --git a/surfsense_backend/app/connectors/google_drive/credentials.py b/surfsense_backend/app/connectors/google_drive/credentials.py index ad618cbe2..7e6335f6d 100644 --- a/surfsense_backend/app/connectors/google_drive/credentials.py +++ b/surfsense_backend/app/connectors/google_drive/credentials.py @@ -43,14 +43,16 @@ async def get_valid_credentials( if not connector: raise ValueError(f"Connector {connector_id} not found") - config_data = connector.config.copy() # Work with a copy to avoid modifying original - + config_data = ( + connector.config.copy() + ) # Work with a copy to avoid modifying original + # Decrypt credentials if they are encrypted token_encrypted = config_data.get("_token_encrypted", False) if token_encrypted and config.SECRET_KEY: try: token_encryption = TokenEncryption(config.SECRET_KEY) - + # Decrypt sensitive fields if config_data.get("token"): config_data["token"] = token_encryption.decrypt_token( @@ -64,7 +66,7 @@ async def get_valid_credentials( config_data["client_secret"] = token_encryption.decrypt_token( config_data["client_secret"] ) - + logger.info( f"Decrypted Google Drive credentials for connector {connector_id}" ) @@ -104,10 +106,10 @@ async def get_valid_credentials( credentials.refresh(Request()) creds_dict = json.loads(credentials.to_json()) - + # Encrypt sensitive credentials before storing token_encrypted = connector.config.get("_token_encrypted", False) - + if token_encrypted and config.SECRET_KEY: token_encryption = TokenEncryption(config.SECRET_KEY) # Encrypt sensitive fields @@ -124,7 +126,7 @@ async def get_valid_credentials( creds_dict["client_secret"] ) creds_dict["_token_encrypted"] = True - + connector.config = creds_dict flag_modified(connector, "config") await session.commit() diff --git a/surfsense_backend/app/connectors/linear_connector.py b/surfsense_backend/app/connectors/linear_connector.py index 82d50cf2d..90ea4d9e9 100644 --- a/surfsense_backend/app/connectors/linear_connector.py +++ b/surfsense_backend/app/connectors/linear_connector.py @@ -44,9 +44,14 @@ class LinearConnector: ValueError: If no Linear access token has been set """ if not self.access_token: - raise ValueError("Linear access token not initialized. Call set_token() first.") + raise ValueError( + "Linear access token not initialized. Call set_token() first." + ) - return {"Content-Type": "application/json", "Authorization": f"Bearer {self.access_token}"} + return { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.access_token}", + } def execute_graphql_query( self, query: str, variables: dict[str, Any] | None = None @@ -66,7 +71,9 @@ class LinearConnector: Exception: If the API request fails """ if not self.access_token: - raise ValueError("Linear access token not initialized. Call set_token() first.") + raise ValueError( + "Linear access token not initialized. Call set_token() first." + ) headers = self.get_headers() payload = {"query": query} @@ -174,9 +181,15 @@ class LinearConnector: """ # Validate date strings if not start_date or start_date.lower() in ("undefined", "null", "none"): - return [], "Invalid start_date: must be a valid date string in YYYY-MM-DD format" + return ( + [], + "Invalid start_date: must be a valid date string in YYYY-MM-DD format", + ) if not end_date or end_date.lower() in ("undefined", "null", "none"): - return [], "Invalid end_date: must be a valid date string in YYYY-MM-DD format" + return ( + [], + "Invalid end_date: must be a valid date string in YYYY-MM-DD format", + ) # Convert date strings to ISO format try: diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index 922f8834b..1d1fa39ad 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -15,13 +15,13 @@ from .google_drive_add_connector_route import ( from .google_gmail_add_connector_route import ( router as google_gmail_add_connector_router, ) +from .linear_add_connector_route import router as linear_add_connector_router from .logs_routes import router as logs_router from .luma_add_connector_route import router as luma_add_connector_router from .new_chat_routes import router as new_chat_router -from .linear_add_connector_route import router as linear_add_connector_router -from .notion_add_connector_route import router as notion_add_connector_router from .new_llm_config_routes import router as new_llm_config_router from .notes_routes import router as notes_router +from .notion_add_connector_route import router as notion_add_connector_router 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 diff --git a/surfsense_backend/app/routes/airtable_add_connector_route.py b/surfsense_backend/app/routes/airtable_add_connector_route.py index 051700eff..9284d89e8 100644 --- a/surfsense_backend/app/routes/airtable_add_connector_route.py +++ b/surfsense_backend/app/routes/airtable_add_connector_route.py @@ -191,7 +191,7 @@ async def airtable_callback( 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( @@ -201,16 +201,12 @@ async def airtable_callback( return RedirectResponse( url=f"{config.NEXT_FRONTEND_URL}/dashboard?error=airtable_oauth_denied" ) - + # Validate required parameters for successful flow if not code: - raise HTTPException( - status_code=400, detail="Missing authorization code" - ) + raise HTTPException(status_code=400, detail="Missing authorization code") if not state: - raise HTTPException( - status_code=400, detail="Missing state parameter" - ) + raise HTTPException(status_code=400, detail="Missing state parameter") # Validate and decode state with signature verification state_manager = get_state_manager() @@ -226,7 +222,7 @@ async def airtable_callback( user_id = UUID(data["user_id"]) space_id = data["space_id"] code_verifier = data.get("code_verifier") - + if not code_verifier: raise HTTPException( status_code=400, detail="Missing code_verifier in state parameter" @@ -273,7 +269,7 @@ async def airtable_callback( token_encryption = get_token_encryption() access_token = token_json.get("access_token") refresh_token = token_json.get("refresh_token") - + if not access_token: raise HTTPException( status_code=400, detail="No access token received from Airtable" @@ -296,7 +292,7 @@ async def airtable_callback( expires_at=expires_at, scope=token_json.get("scope"), ) - + # Mark that tokens are encrypted for backward compatibility credentials_dict = credentials.to_dict() credentials_dict["_token_encrypted"] = True @@ -390,11 +386,11 @@ async def refresh_airtable_token( logger.info(f"Refreshing Airtable token for connector {connector.id}") credentials = AirtableAuthCredentialsBase.from_dict(connector.config) - + # Decrypt tokens if they are encrypted token_encryption = get_token_encryption() is_encrypted = connector.config.get("_token_encrypted", False) - + refresh_token = credentials.refresh_token if is_encrypted and refresh_token: try: @@ -404,7 +400,7 @@ async def refresh_airtable_token( raise HTTPException( status_code=500, detail="Failed to decrypt stored refresh token" ) from e - + auth_header = make_basic_auth_header( config.AIRTABLE_CLIENT_ID, config.AIRTABLE_CLIENT_SECRET ) @@ -444,7 +440,7 @@ async def refresh_airtable_token( # Encrypt new tokens before storing access_token = token_json.get("access_token") new_refresh_token = token_json.get("refresh_token") - + if not access_token: raise HTTPException( status_code=400, detail="No access token received from Airtable refresh" @@ -453,7 +449,9 @@ async def refresh_airtable_token( # Update credentials object with encrypted tokens credentials.access_token = token_encryption.encrypt_token(access_token) if new_refresh_token: - credentials.refresh_token = token_encryption.encrypt_token(new_refresh_token) + credentials.refresh_token = token_encryption.encrypt_token( + new_refresh_token + ) credentials.expires_in = token_json.get("expires_in") credentials.expires_at = expires_at credentials.scope = token_json.get("scope") diff --git a/surfsense_backend/app/routes/google_calendar_add_connector_route.py b/surfsense_backend/app/routes/google_calendar_add_connector_route.py index 7c483305d..6c6ae4e40 100644 --- a/surfsense_backend/app/routes/google_calendar_add_connector_route.py +++ b/surfsense_backend/app/routes/google_calendar_add_connector_route.py @@ -142,13 +142,9 @@ async def calendar_callback( # Validate required parameters for successful flow if not code: - raise HTTPException( - status_code=400, detail="Missing authorization code" - ) + raise HTTPException(status_code=400, detail="Missing authorization code") if not state: - raise HTTPException( - status_code=400, detail="Missing state parameter" - ) + raise HTTPException(status_code=400, detail="Missing state parameter") # Validate and decode state with signature verification state_manager = get_state_manager() @@ -178,7 +174,7 @@ async def calendar_callback( # Encrypt sensitive credentials before storing token_encryption = get_token_encryption() - + # Encrypt sensitive fields: token, refresh_token, client_secret if creds_dict.get("token"): creds_dict["token"] = token_encryption.encrypt_token(creds_dict["token"]) @@ -190,7 +186,7 @@ async def calendar_callback( creds_dict["client_secret"] = token_encryption.encrypt_token( creds_dict["client_secret"] ) - + # Mark that credentials are encrypted for backward compatibility creds_dict["_token_encrypted"] = True diff --git a/surfsense_backend/app/routes/google_drive_add_connector_route.py b/surfsense_backend/app/routes/google_drive_add_connector_route.py index f8c7bb653..6caf3f204 100644 --- a/surfsense_backend/app/routes/google_drive_add_connector_route.py +++ b/surfsense_backend/app/routes/google_drive_add_connector_route.py @@ -68,6 +68,7 @@ def get_token_encryption() -> TokenEncryption: _token_encryption = TokenEncryption(config.SECRET_KEY) return _token_encryption + # Google Drive OAuth scopes SCOPES = [ "https://www.googleapis.com/auth/drive.readonly", # Read-only access to Drive @@ -191,13 +192,9 @@ async def drive_callback( # Validate required parameters for successful flow if not code: - raise HTTPException( - status_code=400, detail="Missing authorization code" - ) + raise HTTPException(status_code=400, detail="Missing authorization code") if not state: - raise HTTPException( - status_code=400, detail="Missing state parameter" - ) + raise HTTPException(status_code=400, detail="Missing state parameter") # Validate and decode state with signature verification state_manager = get_state_manager() @@ -232,7 +229,7 @@ async def drive_callback( # Encrypt sensitive credentials before storing token_encryption = get_token_encryption() - + # Encrypt sensitive fields: token, refresh_token, client_secret if creds_dict.get("token"): creds_dict["token"] = token_encryption.encrypt_token(creds_dict["token"]) @@ -244,7 +241,7 @@ async def drive_callback( creds_dict["client_secret"] = token_encryption.encrypt_token( creds_dict["client_secret"] ) - + # Mark that credentials are encrypted for backward compatibility creds_dict["_token_encrypted"] = True diff --git a/surfsense_backend/app/routes/google_gmail_add_connector_route.py b/surfsense_backend/app/routes/google_gmail_add_connector_route.py index 5be062e99..20a51c1a1 100644 --- a/surfsense_backend/app/routes/google_gmail_add_connector_route.py +++ b/surfsense_backend/app/routes/google_gmail_add_connector_route.py @@ -173,13 +173,9 @@ async def gmail_callback( # Validate required parameters for successful flow if not code: - raise HTTPException( - status_code=400, detail="Missing authorization code" - ) + raise HTTPException(status_code=400, detail="Missing authorization code") if not state: - raise HTTPException( - status_code=400, detail="Missing state parameter" - ) + raise HTTPException(status_code=400, detail="Missing state parameter") # Validate and decode state with signature verification state_manager = get_state_manager() @@ -209,7 +205,7 @@ async def gmail_callback( # Encrypt sensitive credentials before storing token_encryption = get_token_encryption() - + # Encrypt sensitive fields: token, refresh_token, client_secret if creds_dict.get("token"): creds_dict["token"] = token_encryption.encrypt_token(creds_dict["token"]) @@ -221,7 +217,7 @@ async def gmail_callback( creds_dict["client_secret"] = token_encryption.encrypt_token( creds_dict["client_secret"] ) - + # Mark that credentials are encrypted for backward compatibility creds_dict["_token_encrypted"] = True diff --git a/surfsense_backend/app/routes/linear_add_connector_route.py b/surfsense_backend/app/routes/linear_add_connector_route.py index 09e5e66ff..9747d4507 100644 --- a/surfsense_backend/app/routes/linear_add_connector_route.py +++ b/surfsense_backend/app/routes/linear_add_connector_route.py @@ -4,7 +4,6 @@ Linear Connector OAuth Routes. Handles OAuth 2.0 authentication flow for Linear connector. """ -import json import logging from datetime import UTC, datetime, timedelta from uuid import UUID @@ -89,9 +88,7 @@ async def connect_linear(space_id: int, user: User = Depends(current_active_user raise HTTPException(status_code=400, detail="space_id is required") if not config.LINEAR_CLIENT_ID: - raise HTTPException( - status_code=500, detail="Linear OAuth not configured." - ) + raise HTTPException(status_code=500, detail="Linear OAuth not configured.") if not config.SECRET_KEY: raise HTTPException( @@ -115,9 +112,7 @@ async def connect_linear(space_id: int, user: User = Depends(current_active_user auth_url = f"{AUTHORIZATION_URL}?{urlencode(auth_params)}" - logger.info( - f"Generated Linear OAuth URL for user {user.id}, space {space_id}" - ) + logger.info(f"Generated Linear OAuth URL for user {user.id}, space {space_id}") return {"auth_url": auth_url} except Exception as e: @@ -162,7 +157,7 @@ async def linear_callback( 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( @@ -172,16 +167,12 @@ async def linear_callback( return RedirectResponse( url=f"{config.NEXT_FRONTEND_URL}/dashboard?error=linear_oauth_denied" ) - + # Validate required parameters for successful flow if not code: - raise HTTPException( - status_code=400, detail="Missing authorization code" - ) + raise HTTPException(status_code=400, detail="Missing authorization code") if not state: - raise HTTPException( - status_code=400, detail="Missing state parameter" - ) + raise HTTPException(status_code=400, detail="Missing state parameter") # Validate and decode state with signature verification state_manager = get_state_manager() @@ -242,7 +233,7 @@ async def linear_callback( token_encryption = get_token_encryption() access_token = token_json.get("access_token") refresh_token = token_json.get("refresh_token") - + if not access_token: raise HTTPException( status_code=400, detail="No access token received from Linear" @@ -337,4 +328,3 @@ async def linear_callback( raise HTTPException( status_code=500, detail=f"Failed to complete Linear OAuth: {e!s}" ) from e - diff --git a/surfsense_backend/app/routes/notion_add_connector_route.py b/surfsense_backend/app/routes/notion_add_connector_route.py index ba1c3f178..a36fdf7e8 100644 --- a/surfsense_backend/app/routes/notion_add_connector_route.py +++ b/surfsense_backend/app/routes/notion_add_connector_route.py @@ -4,7 +4,6 @@ Notion Connector OAuth Routes. Handles OAuth 2.0 authentication flow for Notion connector. """ -import json import logging from uuid import UUID @@ -85,9 +84,7 @@ async def connect_notion(space_id: int, user: User = Depends(current_active_user raise HTTPException(status_code=400, detail="space_id is required") if not config.NOTION_CLIENT_ID: - raise HTTPException( - status_code=500, detail="Notion OAuth not configured." - ) + raise HTTPException(status_code=500, detail="Notion OAuth not configured.") if not config.SECRET_KEY: raise HTTPException( @@ -111,9 +108,7 @@ async def connect_notion(space_id: int, user: User = Depends(current_active_user auth_url = f"{AUTHORIZATION_URL}?{urlencode(auth_params)}" - logger.info( - f"Generated Notion OAuth URL for user {user.id}, space {space_id}" - ) + logger.info(f"Generated Notion OAuth URL for user {user.id}, space {space_id}") return {"auth_url": auth_url} except Exception as e: @@ -158,7 +153,7 @@ async def notion_callback( 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( @@ -168,16 +163,12 @@ async def notion_callback( return RedirectResponse( url=f"{config.NEXT_FRONTEND_URL}/dashboard?error=notion_oauth_denied" ) - + # Validate required parameters for successful flow if not code: - raise HTTPException( - status_code=400, detail="Missing authorization code" - ) + raise HTTPException(status_code=400, detail="Missing authorization code") if not state: - raise HTTPException( - status_code=400, detail="Missing state parameter" - ) + raise HTTPException(status_code=400, detail="Missing state parameter") # Validate and decode state with signature verification state_manager = get_state_manager() @@ -325,4 +316,3 @@ async def notion_callback( raise HTTPException( status_code=500, detail=f"Failed to complete Notion OAuth: {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 6a9200d15..fc00140be 100644 --- a/surfsense_backend/app/tasks/connector_indexers/airtable_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/airtable_indexer.py @@ -12,13 +12,13 @@ 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 -from app.utils.oauth_security import TokenEncryption from app.utils.document_converters import ( create_document_chunks, generate_content_hash, generate_document_summary, generate_unique_identifier_hash, ) +from app.utils.oauth_security import TokenEncryption from .base import ( calculate_date_range, @@ -86,29 +86,35 @@ async def index_airtable_records( return 0, f"Connector with ID {connector_id} not found" # Create credentials from connector config - config_data = connector.config.copy() # Work with a copy to avoid modifying original - + config_data = ( + connector.config.copy() + ) # Work with a copy to avoid modifying original + # Decrypt tokens if they are encrypted (for backward compatibility) token_encrypted = config_data.get("_token_encrypted", False) if token_encrypted and config.SECRET_KEY: try: token_encryption = TokenEncryption(config.SECRET_KEY) - + # Decrypt access_token if config_data.get("access_token"): if token_encryption.is_encrypted(config_data["access_token"]): config_data["access_token"] = token_encryption.decrypt_token( config_data["access_token"] ) - logger.info(f"Decrypted Airtable access token for connector {connector_id}") - + logger.info( + f"Decrypted Airtable access token for connector {connector_id}" + ) + # Decrypt refresh_token if present if config_data.get("refresh_token"): if token_encryption.is_encrypted(config_data["refresh_token"]): config_data["refresh_token"] = token_encryption.decrypt_token( config_data["refresh_token"] ) - logger.info(f"Decrypted Airtable refresh token for connector {connector_id}") + logger.info( + f"Decrypted Airtable refresh token for connector {connector_id}" + ) except Exception as e: await task_logger.log_task_failure( log_entry, @@ -117,7 +123,7 @@ async def index_airtable_records( {"error_type": "TokenDecryptionError"}, ) return 0, f"Failed to decrypt Airtable tokens: {e!s}" - + try: credentials = AirtableAuthCredentialsBase.from_dict(config_data) except Exception as e: diff --git a/surfsense_backend/app/tasks/connector_indexers/google_calendar_indexer.py b/surfsense_backend/app/tasks/connector_indexers/google_calendar_indexer.py index 5ac850f08..499f01d66 100644 --- a/surfsense_backend/app/tasks/connector_indexers/google_calendar_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/google_calendar_indexer.py @@ -8,7 +8,6 @@ from google.oauth2.credentials import Credentials from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession -from app.config import config from app.connectors.google_calendar_connector import GoogleCalendarConnector from app.db import Document, DocumentType, SearchSourceConnectorType from app.services.llm_service import get_user_long_context_llm @@ -85,7 +84,7 @@ async def index_google_calendar_events( # Get the Google Calendar credentials from the connector config config_data = connector.config - + # Decrypt sensitive credentials if encrypted (for backward compatibility) from app.config import config from app.utils.oauth_security import TokenEncryption @@ -94,7 +93,7 @@ async def index_google_calendar_events( if token_encrypted and config.SECRET_KEY: try: token_encryption = TokenEncryption(config.SECRET_KEY) - + # Decrypt sensitive fields if config_data.get("token"): config_data["token"] = token_encryption.decrypt_token( @@ -108,7 +107,7 @@ async def index_google_calendar_events( config_data["client_secret"] = token_encryption.decrypt_token( config_data["client_secret"] ) - + logger.info( f"Decrypted Google Calendar credentials for connector {connector_id}" ) diff --git a/surfsense_backend/app/tasks/connector_indexers/google_gmail_indexer.py b/surfsense_backend/app/tasks/connector_indexers/google_gmail_indexer.py index 898522047..e10297057 100644 --- a/surfsense_backend/app/tasks/connector_indexers/google_gmail_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/google_gmail_indexer.py @@ -8,7 +8,6 @@ from google.oauth2.credentials import Credentials from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession -from app.config import config from app.connectors.google_gmail_connector import GoogleGmailConnector from app.db import ( Document, @@ -90,7 +89,7 @@ async def index_google_gmail_messages( # Get the Google Gmail credentials from the connector config config_data = connector.config - + # Decrypt sensitive credentials if encrypted (for backward compatibility) from app.config import config from app.utils.oauth_security import TokenEncryption @@ -99,7 +98,7 @@ async def index_google_gmail_messages( if token_encrypted and config.SECRET_KEY: try: token_encryption = TokenEncryption(config.SECRET_KEY) - + # Decrypt sensitive fields if config_data.get("token"): config_data["token"] = token_encryption.decrypt_token( @@ -113,7 +112,7 @@ async def index_google_gmail_messages( config_data["client_secret"] = token_encryption.decrypt_token( config_data["client_secret"] ) - + logger.info( f"Decrypted Google Gmail credentials for connector {connector_id}" ) diff --git a/surfsense_backend/app/tasks/connector_indexers/linear_indexer.py b/surfsense_backend/app/tasks/connector_indexers/linear_indexer.py index 1fe5bf9c7..61aba85df 100644 --- a/surfsense_backend/app/tasks/connector_indexers/linear_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/linear_indexer.py @@ -7,7 +7,6 @@ from datetime import datetime from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession -from app.config import config from app.connectors.linear_connector import LinearConnector from app.db import Document, DocumentType, SearchSourceConnectorType from app.services.llm_service import get_user_long_context_llm @@ -114,7 +113,9 @@ async def index_linear_issues( ): try: token_encryption = TokenEncryption(config.SECRET_KEY) - linear_access_token = token_encryption.decrypt_token(linear_access_token) + linear_access_token = token_encryption.decrypt_token( + linear_access_token + ) logger.info( f"Decrypted Linear access token for connector {connector_id}" ) diff --git a/surfsense_backend/app/tasks/connector_indexers/notion_indexer.py b/surfsense_backend/app/tasks/connector_indexers/notion_indexer.py index 1ce6eaeac..71fc4a7c1 100644 --- a/surfsense_backend/app/tasks/connector_indexers/notion_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/notion_indexer.py @@ -107,11 +107,16 @@ async def index_notion_pages( # Decrypt token if it's encrypted (for backward compatibility) token_encrypted = connector.config.get("_token_encrypted", False) - if token_encrypted or (config.SECRET_KEY and TokenEncryption(config.SECRET_KEY).is_encrypted(notion_token)): + if token_encrypted or ( + config.SECRET_KEY + and TokenEncryption(config.SECRET_KEY).is_encrypted(notion_token) + ): try: token_encryption = TokenEncryption(config.SECRET_KEY) notion_token = token_encryption.decrypt_token(notion_token) - logger.info(f"Decrypted Notion access token for connector {connector_id}") + logger.info( + f"Decrypted Notion access token for connector {connector_id}" + ) except Exception as e: await task_logger.log_task_failure( log_entry, diff --git a/surfsense_backend/app/utils/oauth_security.py b/surfsense_backend/app/utils/oauth_security.py index cf8b767a5..5135cdef4 100644 --- a/surfsense_backend/app/utils/oauth_security.py +++ b/surfsense_backend/app/utils/oauth_security.py @@ -35,7 +35,9 @@ class OAuthStateManager: self.secret_key = secret_key self.max_age_seconds = max_age_seconds - def generate_secure_state(self, space_id: int, user_id: UUID, **extra_fields) -> str: + def generate_secure_state( + self, space_id: int, user_id: UUID, **extra_fields + ) -> str: """ Generate cryptographically signed state parameter. @@ -53,7 +55,7 @@ class OAuthStateManager: "user_id": str(user_id), "timestamp": timestamp, } - + # Add any extra fields (e.g., code_verifier for PKCE) state_payload.update(extra_fields) @@ -97,9 +99,7 @@ class OAuthStateManager: # Verify signature exists signature = data.pop("signature", None) if not signature: - raise HTTPException( - status_code=400, detail="Missing state signature" - ) + raise HTTPException(status_code=400, detail="Missing state signature") # Verify signature payload_str = json.dumps(data, sort_keys=True) @@ -120,9 +120,7 @@ class OAuthStateManager: age = current_time - timestamp if age < 0: - raise HTTPException( - status_code=400, detail="Invalid state timestamp" - ) + raise HTTPException(status_code=400, detail="Invalid state timestamp") if age > self.max_age_seconds: raise HTTPException( @@ -147,15 +145,11 @@ class TokenEncryption: raise ValueError("secret_key is required for token encryption") # Derive Fernet key from secret using SHA256 # Note: In production, consider using HKDF for key derivation - key = base64.urlsafe_b64encode( - hashlib.sha256(secret_key.encode()).digest() - ) + key = base64.urlsafe_b64encode(hashlib.sha256(secret_key.encode()).digest()) try: self.cipher = Fernet(key) except Exception as e: - raise ValueError( - f"Failed to initialize encryption cipher: {e!s}" - ) from e + raise ValueError(f"Failed to initialize encryption cipher: {e!s}") from e def encrypt_token(self, token: str) -> str: """ diff --git a/surfsense_web/components/assistant-ui/connector-popup/components/connector-card.tsx b/surfsense_web/components/assistant-ui/connector-popup/components/connector-card.tsx index e0ce62a8d..855be95a2 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/components/connector-card.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/components/connector-card.tsx @@ -1,13 +1,7 @@ "use client"; import { IconBrandYoutube } from "@tabler/icons-react"; -import { - differenceInDays, - differenceInMinutes, - format, - isToday, - isYesterday, -} from "date-fns"; +import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns"; import { FileText, Loader2 } from "lucide-react"; import type { FC } from "react"; import { Button } from "@/components/ui/button"; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx index ae26bdb5a..d479dda8d 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx @@ -46,7 +46,7 @@ export const IndexingConfigurationView: FC = ({ }) => { const searchParams = useSearchParams(); const isFromOAuth = searchParams.get("view") === "configure"; - + // Get connector-specific config component const ConnectorConfigComponent = useMemo( () => (connector ? getConnectorConfigComponent(connector.connector_type) : null), diff --git a/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx b/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx index ec3177e28..3dd4fd1d0 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx @@ -1,12 +1,6 @@ "use client"; -import { - differenceInDays, - differenceInMinutes, - format, - isToday, - isYesterday, -} from "date-fns"; +import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns"; import { ArrowRight, Cable, Loader2 } from "lucide-react"; import { useRouter } from "next/navigation"; import type { FC } from "react"; diff --git a/surfsense_web/components/assistant-ui/document-upload-popup.tsx b/surfsense_web/components/assistant-ui/document-upload-popup.tsx index 9734954e1..da3b820e5 100644 --- a/surfsense_web/components/assistant-ui/document-upload-popup.tsx +++ b/surfsense_web/components/assistant-ui/document-upload-popup.tsx @@ -99,7 +99,7 @@ const DocumentUploadPopupContent: FC<{ Upload Document - + {/* Fixed Header */}
{/* Upload header */} @@ -120,8 +120,8 @@ const DocumentUploadPopupContent: FC<{
- diff --git a/surfsense_web/components/sources/DocumentUploadTab.tsx b/surfsense_web/components/sources/DocumentUploadTab.tsx index 0062dd2dc..0b7f7b51f 100644 --- a/surfsense_web/components/sources/DocumentUploadTab.tsx +++ b/surfsense_web/components/sources/DocumentUploadTab.tsx @@ -110,7 +110,11 @@ const FILE_TYPE_CONFIG: Record> = { const cardClass = "border border-border bg-slate-400/5 dark:bg-white/5"; -export function DocumentUploadTab({ searchSpaceId, onSuccess, onAccordionStateChange }: DocumentUploadTabProps) { +export function DocumentUploadTab({ + searchSpaceId, + onSuccess, + onAccordionStateChange, +}: DocumentUploadTabProps) { const t = useTranslations("upload_documents"); const router = useRouter(); const [files, setFiles] = useState([]); @@ -157,10 +161,13 @@ export function DocumentUploadTab({ searchSpaceId, onSuccess, onAccordionStateCh const totalFileSize = files.reduce((total, file) => total + file.size, 0); // Track accordion state changes - const handleAccordionChange = useCallback((value: string) => { - setAccordionValue(value); - onAccordionStateChange?.(value === "supported-file-types"); - }, [onAccordionStateChange]); + const handleAccordionChange = useCallback( + (value: string) => { + setAccordionValue(value); + onAccordionStateChange?.(value === "supported-file-types"); + }, + [onAccordionStateChange] + ); const handleUpload = async () => { setUploadProgress(0); @@ -202,7 +209,9 @@ export function DocumentUploadTab({ searchSpaceId, onSuccess, onAccordionStateCh > - {t("file_size_limit")} + + {t("file_size_limit")} +