backend(oauth): persist oauth tokens and add Google Calendar demo endpoint

This commit is contained in:
TLimoges33
2025-08-28 17:09:41 +00:00
parent 8aee66f658
commit 08a9c77b65
3 changed files with 83 additions and 6 deletions
+58 -6
View File
@@ -1,13 +1,14 @@
import os
import time
from fastapi import APIRouter, Request
from starlette.responses import RedirectResponse
from authlib.integrations.starlette_client import OAuth
from . import models
router = APIRouter()
oauth = OAuth()
# Register provider with placeholders; read from env at runtime
# Load config from env at runtime
GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID')
GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET')
BASE_URL = os.getenv('BASE_URL', 'http://localhost:8000')
@@ -21,6 +22,7 @@ if GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET:
client_kwargs={'scope': 'openid email profile https://www.googleapis.com/auth/calendar.events'}
)
@router.get('/oauth/google/login')
async def google_login(request: Request):
if 'google' not in oauth:
@@ -28,12 +30,62 @@ async def google_login(request: Request):
redirect_uri = BASE_URL + '/api/v1/oauth/google/callback'
return await oauth.google.authorize_redirect(request, redirect_uri)
@router.get('/oauth/google/callback')
async def google_callback(request: Request):
"""Handle Google's OAuth callback, persist Integration and OAuthToken records.
This demo stores access/refresh tokens associated to a newly created `Integration` for
the (demo) user. In a real app, you'd associate the integration with the authenticated
user and secure storage for tokens.
"""
if 'google' not in oauth:
return {'error': 'google oauth not configured'}
token = await oauth.google.authorize_access_token(request)
user = await oauth.google.parse_id_token(request, token)
# token contains access_token and refresh_token; persist securely
# For demo, return token info
return {'token': token, 'user': user}
# Try to get userinfo (sub/email) from id_token or userinfo endpoint
userinfo = None
try:
userinfo = await oauth.google.parse_id_token(request, token)
except Exception:
# fallback: try userinfo endpoint
try:
resp = await oauth.google.get('userinfo', token=token)
userinfo = resp.json()
except Exception:
userinfo = {}
# Persist integration + token into DB (demo uses `user_id` query param or 1)
db = models.SessionLocal()
try:
# For demo, allow passing ?user_id= to associate the integration
qs = dict(request.query_params)
user_id = int(qs.get('user_id')) if qs.get('user_id') else 1
# Create or reuse an Integration row for this user+provider
ext_id = userinfo.get('sub') or userinfo.get('id') or None
integration = db.query(models.Integration).filter_by(user_id=user_id, provider='google').first()
if not integration:
integration = models.Integration(user_id=user_id, provider='google', external_id=ext_id, config='{}')
db.add(integration)
db.commit()
db.refresh(integration)
# Persist token (single latest token demo)
expires_at = None
if token.get('expires_in'):
expires_at = int(time.time()) + int(token.get('expires_in'))
oauth_token = models.OAuthToken(
integration_id=integration.id,
access_token=token.get('access_token'),
refresh_token=token.get('refresh_token'),
scope=token.get('scope'),
expires_at=expires_at
)
db.add(oauth_token)
db.commit()
return {'ok': True, 'integration_id': integration.id, 'token_saved': bool(oauth_token.id)}
finally:
db.close()