← 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
)