101:@router.get("/google/start") 102-async def google_start(request: Request, user=Depends(get_current_user)): 103- """Begin Google OAuth: generate state, store in Redis w/ TTL, redirect to Google.""" 104- client = gmail_service.resolve_client_config() 105- state = gmail_service.generate_state() 106- data = {"user_id": user.id, "csrf": state} 107- if _oauth_redis is not None: 108- _oauth_redis.setex(f"oauthstate:{state}", 600, json.dumps(data)) 109- else: 110- # fallback: HMAC-signed state containing user+nonce verified on callback 111- import hmac, hashlib 112- payload = f"{user.id}:{state}" 113- sig = hmac.new(settings.SECRET_KEY.encode(), payload.encode(), hashlib.sha256).hexdigest() 114- request.app.state._pending_oauth = getattr(request.app.state, "_pending_oauth", {}) 115- if len(getattr(request.app.state, "_pending_oauth", {})) > 100: 116- request.app.state._pending_oauth.clear() 117- request.app.state._pending_oauth[state] = {"user_id": user.id, "sig": sig} 118- auth_url = gmail_service.build_authorization_url(client, state) 119- return {"authorization_url": auth_url} 120- 121- 122-@router.get("/google/callback") 123-async def google_callback(request: Request, code: str = "", state: str = "", error: str = "", 124- db: AsyncSession = Depends(get_db)): 125- """Handle Google's response: verify state, exchange code for tokens, store encrypted."""