aivideodubbingapi-r08gi1/app/services/google_oauth_service.py
Automated Action 99937b3fd7 Add comprehensive Google OAuth integration for easy login/signup
Features:
- Complete Google OAuth 2.0 integration with ID token and authorization code flows
- Enhanced User model with Google OAuth fields (google_id, is_google_user, email_verified, profile_picture)
- Google OAuth service for token verification and user info extraction
- Multiple authentication endpoints:
  - GET /auth/google/oauth-url (get OAuth URL for frontend)
  - POST /auth/google/login-with-token (direct ID token login)
  - POST /auth/google/login-with-code (authorization code exchange)
- Smart user handling: creates new users or links existing accounts
- Issues own JWT tokens after Google authentication
- Database migration 004 for Google OAuth fields
- Enhanced login logic to handle Google vs password users
- Comprehensive README with Google OAuth setup instructions
- Frontend integration examples for both OAuth flows

Google OAuth automatically:
- Creates user accounts on first login
- Links existing email accounts to Google
- Extracts profile information (name, picture, locale)
- Verifies email addresses
- Issues secure JWT tokens for API access
2025-06-25 05:49:54 +00:00

121 lines
4.1 KiB
Python

import os
import logging
from typing import Optional, Dict, Any
from google.auth.transport import requests as google_requests
from google.oauth2 import id_token
from google.auth.exceptions import GoogleAuthError
logger = logging.getLogger(__name__)
# Google OAuth configuration
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
if not GOOGLE_CLIENT_ID or not GOOGLE_CLIENT_SECRET:
logger.warning("Google OAuth credentials not configured")
class GoogleOAuthService:
@staticmethod
async def verify_google_token(token: str) -> Optional[Dict[str, Any]]:
"""
Verify Google ID token and return user information
"""
if not GOOGLE_CLIENT_ID:
logger.error("Google Client ID not configured")
return None
try:
# Verify the token
id_info = id_token.verify_oauth2_token(
token,
google_requests.Request(),
GOOGLE_CLIENT_ID
)
# Verify that the token is issued by Google
if id_info['iss'] not in ['accounts.google.com', 'https://accounts.google.com']:
logger.error("Invalid token issuer")
return None
# Extract user information
user_info = {
'google_id': id_info.get('sub'),
'email': id_info.get('email'),
'email_verified': id_info.get('email_verified', False),
'first_name': id_info.get('given_name'),
'last_name': id_info.get('family_name'),
'full_name': id_info.get('name'),
'picture': id_info.get('picture'),
'locale': id_info.get('locale', 'en')
}
logger.info(f"Successfully verified Google token for user: {user_info['email']}")
return user_info
except ValueError as e:
logger.error(f"Invalid Google token: {e}")
return None
except GoogleAuthError as e:
logger.error(f"Google auth error: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error verifying Google token: {e}")
return None
@staticmethod
def get_google_oauth_url() -> Optional[str]:
"""
Generate Google OAuth URL for frontend to redirect users
"""
if not GOOGLE_CLIENT_ID:
return None
redirect_uri = os.getenv("GOOGLE_REDIRECT_URI", "http://localhost:3000/auth/google/callback")
oauth_url = (
f"https://accounts.google.com/o/oauth2/auth?"
f"client_id={GOOGLE_CLIENT_ID}&"
f"redirect_uri={redirect_uri}&"
f"scope=openid email profile&"
f"response_type=code&"
f"access_type=offline&"
f"prompt=consent"
)
return oauth_url
@staticmethod
async def exchange_code_for_token(code: str, redirect_uri: str) -> Optional[str]:
"""
Exchange authorization code for ID token
"""
if not GOOGLE_CLIENT_ID or not GOOGLE_CLIENT_SECRET:
logger.error("Google OAuth credentials not configured")
return None
try:
import requests
token_url = "https://oauth2.googleapis.com/token"
data = {
'client_id': GOOGLE_CLIENT_ID,
'client_secret': GOOGLE_CLIENT_SECRET,
'code': code,
'grant_type': 'authorization_code',
'redirect_uri': redirect_uri
}
response = requests.post(token_url, data=data)
if response.status_code == 200:
token_data = response.json()
return token_data.get('id_token')
else:
logger.error(f"Failed to exchange code for token: {response.text}")
return None
except Exception as e:
logger.error(f"Error exchanging code for token: {e}")
return None