phonegpu-space / app /auth /google_oauth.py
josephrw's picture
Upload folder using huggingface_hub
d958e80 verified
Raw
History Blame Contribute Delete
3.35 kB
"""
Google OAuth Authentication
"""
from fastapi import Request, HTTPException
from fastapi.responses import RedirectResponse
from typing import Optional
import httpx
import json
import secrets
from datetime import datetime, timedelta
class GoogleOAuth:
"""Google OAuth 2.0 authentication handler."""
def __init__(self, client_id: str, client_secret: str, redirect_uri: str):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
self.auth_url = "https://accounts.google.com/o/oauth2/v2/auth"
self.token_url = "https://oauth2.googleapis.com/token"
self.userinfo_url = "https://www.googleapis.com/oauth2/v2/userinfo"
self._state_store: dict = {} # state -> timestamp
def get_auth_url(self, state: Optional[str] = None) -> str:
"""Generate Google OAuth authorization URL."""
if not state:
state = secrets.token_urlsafe(32)
self._state_store[state] = datetime.utcnow()
params = {
"client_id": self.client_id,
"redirect_uri": self.redirect_uri,
"response_type": "code",
"scope": "openid email profile",
"state": state,
"access_type": "offline",
"prompt": "consent"
}
return f"{self.auth_url}?{'&'.join(f'{k}={v}' for k, v in params.items())}"
def verify_state(self, state: str) -> bool:
"""Verify OAuth state parameter to prevent CSRF."""
if state not in self._state_store:
return False
# State expires after 10 minutes
if datetime.utcnow() - self._state_store[state] > timedelta(minutes=10):
del self._state_store[state]
return False
del self._state_store[state]
return True
async def exchange_code_for_token(self, code: str) -> dict:
"""Exchange authorization code for access token."""
data = {
"code": code,
"client_id": self.client_id,
"client_secret": self.client_secret,
"redirect_uri": self.redirect_uri,
"grant_type": "authorization_code"
}
async with httpx.AsyncClient() as client:
response = await client.post(self.token_url, data=data)
response.raise_for_status()
return response.json()
async def get_user_info(self, access_token: str) -> dict:
"""Get user information from Google."""
async with httpx.AsyncClient() as client:
response = await client.get(
self.userinfo_url,
headers={"Authorization": f"Bearer {access_token}"}
)
response.raise_for_status()
return response.json()
# Global OAuth instance
_google_oauth: Optional[GoogleOAuth] = None
def get_google_oauth() -> GoogleOAuth:
"""Get global Google OAuth instance."""
global _google_oauth
if _google_oauth is None:
import os
_google_oauth = GoogleOAuth(
client_id=os.getenv("GOOGLE_CLIENT_ID", ""),
client_secret=os.getenv("GOOGLE_CLIENT_SECRET", ""),
redirect_uri=os.getenv("GOOGLE_REDIRECT_URI", "http://localhost:7860/auth/callback")
)
return _google_oauth