Tech Stack Advisor - Code Viewer

← Back to File Tree

google_oauth.py

Language: python | Path: backend/src/core/google_oauth.py | Lines: 133
"""Google OAuth 2.0 authentication helper."""
import requests
from typing import Optional, Dict, Any
from urllib.parse import urlencode

from .config import settings
from .logging import get_logger

logger = get_logger(__name__)

# Google OAuth endpoints
GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
GOOGLE_USER_INFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo"


def get_google_auth_url(state: str) -> str:
    """Generate Google OAuth authorization URL.

    Args:
        state: Random state string for CSRF protection

    Returns:
        Authorization URL to redirect user to
    """
    if not settings.google_client_id:
        logger.error("google_oauth_not_configured", message="GOOGLE_CLIENT_ID not set")
        raise ValueError("Google OAuth is not configured")

    params = {
        "client_id": settings.google_client_id,
        "redirect_uri": settings.google_redirect_uri,
        "response_type": "code",
        "scope": "openid email profile",
        "state": state,
        "access_type": "online",  # We don't need refresh tokens
        "prompt": "select_account",  # Let user choose account
    }

    url = f"{GOOGLE_AUTH_URL}?{urlencode(params)}"
    logger.info("google_auth_url_generated", state=state)
    return url


def exchange_code_for_token(code: str) -> Optional[str]:
    """Exchange authorization code for access token.

    Args:
        code: Authorization code from Google

    Returns:
        Access token if successful, None otherwise
    """
    if not settings.google_client_id or not settings.google_client_secret:
        logger.error("google_oauth_not_configured", message="Google OAuth credentials not set")
        return None

    data = {
        "code": code,
        "client_id": settings.google_client_id,
        "client_secret": settings.google_client_secret,
        "redirect_uri": settings.google_redirect_uri,
        "grant_type": "authorization_code",
    }

    try:
        response = requests.post(GOOGLE_TOKEN_URL, data=data, timeout=10)
        response.raise_for_status()

        token_data = response.json()
        access_token = token_data.get("access_token")

        if access_token:
            logger.info("google_token_exchanged")
            return access_token
        else:
            logger.error("google_token_exchange_failed", error="No access_token in response")
            return None

    except requests.RequestException as e:
        logger.error("google_token_exchange_error", error=str(e))
        return None


def get_user_info(access_token: str) -> Optional[Dict[str, Any]]:
    """Get user information from Google using access token.

    Args:
        access_token: Google OAuth access token

    Returns:
        User info dict with email, name, google_id if successful, None otherwise
    """
    headers = {"Authorization": f"Bearer {access_token}"}

    try:
        response = requests.get(GOOGLE_USER_INFO_URL, headers=headers, timeout=10)
        response.raise_for_status()

        user_data = response.json()

        # Extract relevant user information
        user_info = {
            "email": user_data.get("email"),
            "name": user_data.get("name"),
            "google_id": user_data.get("id"),
            "picture": user_data.get("picture"),
            "verified_email": user_data.get("verified_email", False),
        }

        if not user_info["email"]:
            logger.error("google_user_info_missing_email")
            return None

        logger.info("google_user_info_retrieved", email=user_info["email"])
        return user_info

    except requests.RequestException as e:
        logger.error("google_user_info_error", error=str(e))
        return None


def is_oauth_configured() -> bool:
    """Check if Google OAuth is properly configured.

    Returns:
        True if configured, False otherwise
    """
    return bool(
        settings.google_client_id
        and settings.google_client_secret
        and settings.google_redirect_uri
    )