feat: add fetch_google_user_email and update Google OAuth routes

This commit is contained in:
CREDO23 2026-01-07 08:15:56 +02:00
parent 4c6a782cec
commit 932222bff1
4 changed files with 65 additions and 25 deletions

View file

@ -6,6 +6,7 @@ Allows fetching emails from Gmail mailbox using Google OAuth credentials.
import base64 import base64
import json import json
import logging
import re import re
from typing import Any from typing import Any
@ -21,6 +22,34 @@ from app.db import (
SearchSourceConnectorType, 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 GoogleGmailConnector:
"""Class for retrieving emails from Gmail using Google OAuth credentials.""" """Class for retrieving emails from Gmail using Google OAuth credentials."""

View file

@ -15,6 +15,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select from sqlalchemy.future import select
from app.config import config from app.config import config
from app.connectors.google_gmail_connector import fetch_google_user_email
from app.db import ( from app.db import (
SearchSourceConnector, SearchSourceConnector,
SearchSourceConnectorType, SearchSourceConnectorType,
@ -22,8 +23,8 @@ from app.db import (
get_async_session, get_async_session,
) )
from app.users import current_active_user 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.oauth_security import OAuthStateManager, TokenEncryption
from app.utils.connector_naming import generate_unique_connector_name, extract_identifier_from_credentials
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -173,6 +174,9 @@ async def calendar_callback(
creds = flow.credentials creds = flow.credentials
creds_dict = json.loads(creds.to_json()) 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 # Encrypt sensitive credentials before storing
token_encryption = get_token_encryption() token_encryption = get_token_encryption()
@ -192,14 +196,13 @@ async def calendar_callback(
creds_dict["_token_encrypted"] = True creds_dict["_token_encrypted"] = True
try: try:
# Generate a unique, user-friendly connector name
# Extract unique identifier from connector credentials connector_name = await generate_unique_connector_name(
connector_identifier = extract_identifier_from_credentials( session,
SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR, creds_dict SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR,
) space_id,
# Generate a unique, user-friendly connector name from credentials/account info user_id,
connector_name = generate_unique_connector_name( user_email,
SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR, connector_identifier
) )
db_connector = SearchSourceConnector( db_connector = SearchSourceConnector(
name=connector_name, name=connector_name,

View file

@ -29,6 +29,7 @@ from app.connectors.google_drive import (
get_start_page_token, get_start_page_token,
list_folder_contents, list_folder_contents,
) )
from app.connectors.google_gmail_connector import fetch_google_user_email
from app.db import ( from app.db import (
SearchSourceConnector, SearchSourceConnector,
SearchSourceConnectorType, SearchSourceConnectorType,
@ -36,8 +37,8 @@ from app.db import (
get_async_session, get_async_session,
) )
from app.users import current_active_user 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.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 # Relax token scope validation for Google OAuth
os.environ["OAUTHLIB_RELAX_TOKEN_SCOPE"] = "1" os.environ["OAUTHLIB_RELAX_TOKEN_SCOPE"] = "1"
@ -228,6 +229,9 @@ async def drive_callback(
creds = flow.credentials creds = flow.credentials
creds_dict = json.loads(creds.to_json()) 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 # Encrypt sensitive credentials before storing
token_encryption = get_token_encryption() token_encryption = get_token_encryption()
@ -246,13 +250,13 @@ async def drive_callback(
# Mark that credentials are encrypted for backward compatibility # Mark that credentials are encrypted for backward compatibility
creds_dict["_token_encrypted"] = True creds_dict["_token_encrypted"] = True
# Extract unique identifier from connector credentials # Generate a unique, user-friendly connector name
connector_identifier = extract_identifier_from_credentials( connector_name = await generate_unique_connector_name(
SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR, creds_dict session,
) SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR,
# Generate a unique, user-friendly connector name from credentials/account info space_id,
connector_name = generate_unique_connector_name( user_id,
SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR, connector_identifier user_email,
) )
db_connector = SearchSourceConnector( db_connector = SearchSourceConnector(

View file

@ -15,6 +15,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select from sqlalchemy.future import select
from app.config import config from app.config import config
from app.connectors.google_gmail_connector import fetch_google_user_email
from app.db import ( from app.db import (
SearchSourceConnector, SearchSourceConnector,
SearchSourceConnectorType, SearchSourceConnectorType,
@ -22,8 +23,8 @@ from app.db import (
get_async_session, get_async_session,
) )
from app.users import current_active_user 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.oauth_security import OAuthStateManager, TokenEncryption
from app.utils.connector_naming import generate_unique_connector_name, extract_identifier_from_credentials
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -204,6 +205,9 @@ async def gmail_callback(
creds = flow.credentials creds = flow.credentials
creds_dict = json.loads(creds.to_json()) 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 # Encrypt sensitive credentials before storing
token_encryption = get_token_encryption() token_encryption = get_token_encryption()
@ -223,13 +227,13 @@ async def gmail_callback(
creds_dict["_token_encrypted"] = True creds_dict["_token_encrypted"] = True
try: try:
# Extract unique identifier from connector credentials # Generate a unique, user-friendly connector name
connector_identifier = extract_identifier_from_credentials( connector_name = await generate_unique_connector_name(
SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR, creds_dict session,
) SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR,
# Generate a unique, user-friendly connector name from credentials/account info space_id,
connector_name = generate_unique_connector_name( user_id,
SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR, connector_identifier user_email,
) )
db_connector = SearchSourceConnector( db_connector = SearchSourceConnector(
name=connector_name, name=connector_name,