mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
chore: ran both frontend and backend linting
This commit is contained in:
parent
45489423d1
commit
645e849d93
21 changed files with 148 additions and 155 deletions
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
|
|||
}) => {
|
||||
const searchParams = useSearchParams();
|
||||
const isFromOAuth = searchParams.get("view") === "configure";
|
||||
|
||||
|
||||
// Get connector-specific config component
|
||||
const ConnectorConfigComponent = useMemo(
|
||||
() => (connector ? getConnectorConfigComponent(connector.connector_type) : null),
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ const DocumentUploadPopupContent: FC<{
|
|||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-4xl w-[95vw] sm:w-full max-h-[calc(100vh-2rem)] sm:h-[85vh] flex flex-col p-0 gap-0 overflow-hidden border border-border bg-muted text-foreground [&>button]:right-3 sm:[&>button]:right-12 [&>button]:top-4 sm:[&>button]:top-10 [&>button]:opacity-80 hover:[&>button]:opacity-100 [&>button]:z-[100] [&>button_svg]:size-4 sm:[&>button_svg]:size-5">
|
||||
<DialogTitle className="sr-only">Upload Document</DialogTitle>
|
||||
|
||||
|
||||
{/* Fixed Header */}
|
||||
<div className="flex-shrink-0 px-4 sm:px-12 pt-6 sm:pt-10 transition-shadow duration-200 relative z-10">
|
||||
{/* Upload header */}
|
||||
|
|
@ -120,8 +120,8 @@ const DocumentUploadPopupContent: FC<{
|
|||
<div className="flex-1 min-h-0 relative overflow-hidden">
|
||||
<div className={`h-full ${isAccordionExpanded ? "overflow-y-auto" : ""}`}>
|
||||
<div className="px-6 sm:px-12 pb-5 sm:pb-16">
|
||||
<DocumentUploadTab
|
||||
searchSpaceId={searchSpaceId}
|
||||
<DocumentUploadTab
|
||||
searchSpaceId={searchSpaceId}
|
||||
onSuccess={handleSuccess}
|
||||
onAccordionStateChange={setIsAccordionExpanded}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -110,7 +110,11 @@ const FILE_TYPE_CONFIG: Record<string, Record<string, string[]>> = {
|
|||
|
||||
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<File[]>([]);
|
||||
|
|
@ -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
|
|||
>
|
||||
<Alert className="border border-border bg-slate-400/5 dark:bg-white/5 flex items-start gap-3 [&>svg]:relative [&>svg]:left-0 [&>svg]:top-0 [&>svg~*]:pl-0">
|
||||
<Info className="h-4 w-4 shrink-0 mt-0.5" />
|
||||
<AlertDescription className="text-xs sm:text-sm leading-relaxed pt-0.5">{t("file_size_limit")}</AlertDescription>
|
||||
<AlertDescription className="text-xs sm:text-sm leading-relaxed pt-0.5">
|
||||
{t("file_size_limit")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Card className={`relative overflow-hidden ${cardClass}`}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue