Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions surfsense_backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,22 @@ GOOGLE_OAUTH_CLIENT_SECRET=GOCSV
GOOGLE_CALENDAR_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/calendar/connector/callback
GOOGLE_GMAIL_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/gmail/connector/callback
GOOGLE_DRIVE_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/drive/connector/callback
GOOGLE_DRIVE_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/drive/connector/callback

# Airtable OAuth for Aitable Connector
# OAuth for Aitable Connector
AIRTABLE_CLIENT_ID=your_airtable_client_id
AIRTABLE_CLIENT_SECRET=your_airtable_client_secret
AIRTABLE_REDIRECT_URI=http://localhost:8000/api/v1/auth/airtable/connector/callback

# OAuth for Linear Connector
LINEAR_CLIENT_ID=your_linear_client_id
LINEAR_CLIENT_SECRET=your_linear_client_secret
LINEAR_REDIRECT_URI=http://localhost:8000/api/v1/auth/linear/connector/callback

# OAuth for Notion Connector
NOTION_CLIENT_ID=your_notion_client_id
NOTION_CLIENT_SECRET=your_notion_client_secret
NOTION_REDIRECT_URI=http://localhost:8000/api/v1/auth/notion/connector/callback

# Embedding Model
# Examples:
# # Get sentence transformers embeddings
Expand Down
10 changes: 10 additions & 0 deletions surfsense_backend/app/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ class Config:
AIRTABLE_CLIENT_SECRET = os.getenv("AIRTABLE_CLIENT_SECRET")
AIRTABLE_REDIRECT_URI = os.getenv("AIRTABLE_REDIRECT_URI")

# Notion OAuth
NOTION_CLIENT_ID = os.getenv("NOTION_CLIENT_ID")
NOTION_CLIENT_SECRET = os.getenv("NOTION_CLIENT_SECRET")
NOTION_REDIRECT_URI = os.getenv("NOTION_REDIRECT_URI")

# Linear OAuth
LINEAR_CLIENT_ID = os.getenv("LINEAR_CLIENT_ID")
LINEAR_CLIENT_SECRET = os.getenv("LINEAR_CLIENT_SECRET")
LINEAR_REDIRECT_URI = os.getenv("LINEAR_REDIRECT_URI")

# LLM instances are now managed per-user through the LLMConfig system
# Legacy environment variables removed in favor of user-specific configurations

Expand Down
43 changes: 42 additions & 1 deletion surfsense_backend/app/connectors/google_calendar_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,36 @@ async def _get_credentials(
raise RuntimeError(
"GOOGLE_CALENDAR_CONNECTOR connector not found; cannot persist refreshed token."
)
connector.config = json.loads(self._credentials.to_json())

# 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
if creds_dict.get("token"):
creds_dict["token"] = token_encryption.encrypt_token(
creds_dict["token"]
)
if creds_dict.get("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["_token_encrypted"] = True

connector.config = creds_dict
flag_modified(connector, "config")
await self._session.commit()
except Exception as e:
Expand Down Expand Up @@ -182,6 +211,18 @@ async def get_all_primary_calendar_events(
Tuple containing (events list, error message or None)
"""
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",
)
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",
)

service = await self._get_service()

# Parse both dates
Expand Down
65 changes: 63 additions & 2 deletions surfsense_backend/app/connectors/google_drive/credentials.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Google Drive OAuth credential management."""

import json
import logging
from datetime import datetime

from google.auth.transport.requests import Request
Expand All @@ -9,7 +10,11 @@
from sqlalchemy.future import select
from sqlalchemy.orm.attributes import flag_modified

from app.config import config
from app.db import SearchSourceConnector
from app.utils.oauth_security import TokenEncryption

logger = logging.getLogger(__name__)


async def get_valid_credentials(
Expand Down Expand Up @@ -38,7 +43,41 @@ async def get_valid_credentials(
if not connector:
raise ValueError(f"Connector {connector_id} not found")

config_data = connector.config
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(
config_data["token"]
)
if config_data.get("refresh_token"):
config_data["refresh_token"] = token_encryption.decrypt_token(
config_data["refresh_token"]
)
if config_data.get("client_secret"):
config_data["client_secret"] = token_encryption.decrypt_token(
config_data["client_secret"]
)

logger.info(
f"Decrypted Google Drive credentials for connector {connector_id}"
)
except Exception as e:
logger.error(
f"Failed to decrypt Google Drive credentials for connector {connector_id}: {e!s}"
)
raise ValueError(
f"Failed to decrypt Google Drive credentials: {e!s}"
) from e

exp = config_data.get("expiry", "").replace("Z", "")

if not all(
Expand Down Expand Up @@ -66,7 +105,29 @@ async def get_valid_credentials(
try:
credentials.refresh(Request())

connector.config = json.loads(credentials.to_json())
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
if creds_dict.get("token"):
creds_dict["token"] = token_encryption.encrypt_token(
creds_dict["token"]
)
if creds_dict.get("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["_token_encrypted"] = True

connector.config = creds_dict
flag_modified(connector, "config")
await session.commit()

Expand Down
Loading
Loading