diff --git a/surfsense_backend/app/connectors/google_gmail_connector.py b/surfsense_backend/app/connectors/google_gmail_connector.py index 402337448..10008ad73 100644 --- a/surfsense_backend/app/connectors/google_gmail_connector.py +++ b/surfsense_backend/app/connectors/google_gmail_connector.py @@ -6,6 +6,7 @@ Allows fetching emails from Gmail mailbox using Google OAuth credentials. import base64 import json +import logging import re from typing import Any @@ -21,6 +22,34 @@ from app.db import ( SearchSourceConnectorType, ) +logger = logging.getLogger(__name__) + + +def fetch_google_user_email(credentials: Credentials) -> str | None: + """ + Fetch user email from Gmail API using Google credentials. + + Uses the Gmail users.getProfile endpoint which returns the authenticated + user's email address. + + Args: + credentials: Google OAuth Credentials object (not encrypted) + + Returns: + User's email address or None if fetch fails + """ + try: + service = build("gmail", "v1", credentials=credentials) + profile = service.users().getProfile(userId="me").execute() + email = profile.get("emailAddress") + if email: + logger.debug(f"Fetched Google user email: {email}") + return email + return None + except Exception as e: + logger.warning(f"Error fetching Google user email: {e!s}") + return None + class GoogleGmailConnector: """Class for retrieving emails from Gmail using Google OAuth credentials.""" 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 73d50cb7e..7210efae0 100644 --- a/surfsense_backend/app/routes/google_calendar_add_connector_route.py +++ b/surfsense_backend/app/routes/google_calendar_add_connector_route.py @@ -15,6 +15,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from app.config import config +from app.connectors.google_gmail_connector import fetch_google_user_email from app.db import ( SearchSourceConnector, SearchSourceConnectorType, @@ -22,8 +23,8 @@ from app.db import ( get_async_session, ) 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__) @@ -173,6 +174,9 @@ async def calendar_callback( creds = flow.credentials creds_dict = json.loads(creds.to_json()) + # Fetch user email before encrypting credentials + user_email = fetch_google_user_email(creds) + # Encrypt sensitive credentials before storing token_encryption = get_token_encryption() @@ -192,14 +196,13 @@ async def calendar_callback( creds_dict["_token_encrypted"] = True try: - - # Extract unique identifier from connector credentials - connector_identifier = extract_identifier_from_credentials( - SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR, creds_dict - ) - # Generate a unique, user-friendly connector name from credentials/account info - connector_name = generate_unique_connector_name( - SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR, connector_identifier + # Generate a unique, user-friendly connector name + connector_name = await generate_unique_connector_name( + session, + SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR, + space_id, + user_id, + user_email, ) db_connector = SearchSourceConnector( name=connector_name, 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 3e9800ed1..e63e4df30 100644 --- a/surfsense_backend/app/routes/google_drive_add_connector_route.py +++ b/surfsense_backend/app/routes/google_drive_add_connector_route.py @@ -29,6 +29,7 @@ from app.connectors.google_drive import ( get_start_page_token, list_folder_contents, ) +from app.connectors.google_gmail_connector import fetch_google_user_email from app.db import ( SearchSourceConnector, SearchSourceConnectorType, @@ -36,8 +37,8 @@ from app.db import ( get_async_session, ) 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 # Relax token scope validation for Google OAuth os.environ["OAUTHLIB_RELAX_TOKEN_SCOPE"] = "1" @@ -228,6 +229,9 @@ async def drive_callback( creds = flow.credentials creds_dict = json.loads(creds.to_json()) + # Fetch user email before encrypting credentials + user_email = fetch_google_user_email(creds) + # Encrypt sensitive credentials before storing token_encryption = get_token_encryption() @@ -246,13 +250,13 @@ async def drive_callback( # Mark that credentials are encrypted for backward compatibility creds_dict["_token_encrypted"] = True - # Extract unique identifier from connector credentials - connector_identifier = extract_identifier_from_credentials( - SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR, creds_dict - ) - # Generate a unique, user-friendly connector name from credentials/account info - connector_name = generate_unique_connector_name( - SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR, connector_identifier + # Generate a unique, user-friendly connector name + connector_name = await generate_unique_connector_name( + session, + SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR, + space_id, + user_id, + user_email, ) db_connector = SearchSourceConnector( 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 01bca39f4..a6071ca53 100644 --- a/surfsense_backend/app/routes/google_gmail_add_connector_route.py +++ b/surfsense_backend/app/routes/google_gmail_add_connector_route.py @@ -15,6 +15,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from app.config import config +from app.connectors.google_gmail_connector import fetch_google_user_email from app.db import ( SearchSourceConnector, SearchSourceConnectorType, @@ -22,8 +23,8 @@ from app.db import ( get_async_session, ) 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__) @@ -204,6 +205,9 @@ async def gmail_callback( creds = flow.credentials creds_dict = json.loads(creds.to_json()) + # Fetch user email before encrypting credentials + user_email = fetch_google_user_email(creds) + # Encrypt sensitive credentials before storing token_encryption = get_token_encryption() @@ -223,13 +227,13 @@ async def gmail_callback( creds_dict["_token_encrypted"] = True try: - # Extract unique identifier from connector credentials - connector_identifier = extract_identifier_from_credentials( - SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR, creds_dict - ) - # Generate a unique, user-friendly connector name from credentials/account info - connector_name = generate_unique_connector_name( - SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR, connector_identifier + # Generate a unique, user-friendly connector name + connector_name = await generate_unique_connector_name( + session, + SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR, + space_id, + user_id, + user_email, ) db_connector = SearchSourceConnector( name=connector_name,