Fix google auth

This commit is contained in:
dvirlabs 2026-02-22 05:24:40 +02:00
parent 941d005d6e
commit e48a5a2d2c
4 changed files with 138 additions and 22 deletions

View File

@ -1,6 +1,7 @@
from fastapi import FastAPI, HTTPException, Header, Depends, Request from fastapi import FastAPI, HTTPException, Header, Depends, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from starlette.middleware.sessions import SessionMiddleware
from pydantic import BaseModel, Field, ConfigDict from pydantic import BaseModel, Field, ConfigDict
from typing import List, Optional from typing import List, Optional
from datetime import datetime from datetime import datetime
@ -35,15 +36,56 @@ oauth.register(
allowed_origins = ["http://localhost:5173", "https://tasko.dvirlabs.com"] allowed_origins = ["http://localhost:5173", "https://tasko.dvirlabs.com"]
# Configure CORS # Environment-aware configuration
ENVIRONMENT = os.getenv('ENVIRONMENT', 'development')
is_production = ENVIRONMENT == 'production'
is_development = ENVIRONMENT == 'development'
# Configure Session Middleware (required for OAuth state/nonce storage)
# Generate a strong SECRET_KEY: python -c "import secrets; print(secrets.token_hex(32))"
SESSION_SECRET = os.getenv('SESSION_SECRET', 'dev-secret-change-in-production')
# Session cookie configuration - environment aware
session_config = {
"secret_key": SESSION_SECRET,
"session_cookie": "tasko_session",
"max_age": 3600, # 1 hour
"path": "/", # Available on all routes (required for OAuth callback)
"same_site": "lax", # Allows OAuth redirects while preventing CSRF
"https_only": is_production, # False for HTTP (dev), True for HTTPS (prod)
}
# Development-specific: Ensure cookies work on localhost
if is_development:
session_config["domain"] = None # Don't set domain for localhost
app.add_middleware(SessionMiddleware, **session_config)
# Log session configuration in development
if is_development:
print(f"🔐 Session Configuration (Development Mode):")
print(f" - Cookie Name: {session_config['session_cookie']}")
print(f" - Path: {session_config['path']}")
print(f" - SameSite: {session_config['same_site']}")
print(f" - HTTPS Only: {session_config['https_only']}")
print(f" - Domain: {session_config.get('domain', 'None (localhost)')}")
# Configure CORS - MUST use specific origins (not "*") when allow_credentials=True
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], allow_origins=allowed_origins, # Specific origins required for credentials
allow_credentials=True, allow_credentials=True, # Required for session cookies
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
expose_headers=["*"], # Allow frontend to read all response headers
) )
# Log startup info
if is_development:
print(f"🚀 Tasko API starting in DEVELOPMENT mode")
print(f" - CORS Origins: {allowed_origins}")
print(f" - Allow Credentials: True (session cookies enabled)")
# Pydantic Models for API # Pydantic Models for API
class UserResponse(BaseModel): class UserResponse(BaseModel):
model_config = ConfigDict(from_attributes=True) model_config = ConfigDict(from_attributes=True)
@ -228,14 +270,56 @@ def logout(authorization: Optional[str] = Header(None), db: Session = Depends(ge
@app.get("/auth/google") @app.get("/auth/google")
async def google_login(request: Request): async def google_login(request: Request):
"""Initiate Google OAuth login""" """Initiate Google OAuth login - DIRECT BROWSER REDIRECT (not fetch)"""
redirect_uri = os.getenv('GOOGLE_REDIRECT_URI', 'http://localhost:8000/auth/google/callback') redirect_uri = os.getenv('GOOGLE_REDIRECT_URI', 'http://localhost:8000/auth/google/callback')
return await oauth.google.authorize_redirect(request, redirect_uri)
# Debug logging for development
if is_development:
print(f"\n🔑 OAuth Login initiated (/auth/google):")
print(f" - Redirect URI: {redirect_uri}")
print(f" - Request URL: {request.url}")
print(f" - Request method: {request.method}")
print(f" - Request headers Cookie: {request.headers.get('cookie', 'NONE')}")
print(f" - Session available: {hasattr(request, 'session')}")
if hasattr(request, 'session'):
print(f" - Session keys BEFORE: {list(request.session.keys())}")
# This will set session state and redirect to Google
response = await oauth.google.authorize_redirect(request, redirect_uri)
if is_development:
print(f" - Session keys AFTER: {list(request.session.keys())}")
print(f" - Response status: {response.status_code}")
print(f" - Response Set-Cookie: {response.headers.get('set-cookie', 'NONE')}")
print(f" - Response Location: {response.headers.get('location', 'NONE')[:100]}...")
return response
@app.get("/auth/google/callback") @app.get("/auth/google/callback")
async def google_callback(request: Request, db: Session = Depends(get_db)): async def google_callback(request: Request, db: Session = Depends(get_db)):
"""Handle Google OAuth callback""" """Handle Google OAuth callback"""
try: try:
# Debug logging for development
if is_development:
print(f"\n🔄 OAuth Callback received (/auth/google/callback):")
print(f" - Request URL: {request.url}")
print(f" - Request method: {request.method}")
print(f" - Request headers Cookie: {request.headers.get('cookie', 'NONE')}")
print(f" - Request query params: {dict(request.query_params)}")
print(f" - Cookies from request.cookies: {list(request.cookies.keys())}")
print(f" - Session available: {hasattr(request, 'session')}")
if hasattr(request, 'session'):
session_keys = list(request.session.keys())
print(f" - Session keys: {session_keys}")
# Print state for debugging
for key in session_keys:
if 'state' in key.lower():
value_str = str(request.session[key])
if len(value_str) > 100:
print(f" - Session[{key}]: {value_str[:100]}...")
else:
print(f" - Session[{key}]: {value_str}")
# Get access token from Google # Get access token from Google
token = await oauth.google.authorize_access_token(request) token = await oauth.google.authorize_access_token(request)
@ -297,11 +381,21 @@ async def google_callback(request: Request, db: Session = Depends(get_db)):
db.add(new_token) db.add(new_token)
db.commit() db.commit()
if is_development:
print(f"✅ OAuth Login SUCCESS!")
print(f" - User: {user.email} (ID: {user.id})")
print(f" - Token generated: {token_str[:20]}...")
print(f" - Redirecting to frontend with token")
# Redirect to frontend with token # Redirect to frontend with token
frontend_url = os.getenv('FRONTEND_URL', 'http://localhost:5173') frontend_url = os.getenv('FRONTEND_URL', 'http://localhost:5173')
return RedirectResponse(url=f"{frontend_url}?token={token_str}&user={user.id}") return RedirectResponse(url=f"{frontend_url}?token={token_str}&user={user.id}")
except Exception as e: except Exception as e:
if is_development:
print(f"❌ OAuth Login FAILED: {str(e)}")
import traceback
traceback.print_exc()
raise HTTPException(status_code=400, detail=f"Authentication failed: {str(e)}") raise HTTPException(status_code=400, detail=f"Authentication failed: {str(e)}")
@app.get("/auth/google/url") @app.get("/auth/google/url")

View File

@ -2,7 +2,8 @@ import { useState, useEffect } from 'react'
import Auth from './Auth' import Auth from './Auth'
import './App.css' import './App.css'
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' // Use relative URLs to leverage Vite proxy (same-origin = cookies work!)
const API_URL = import.meta.env.VITE_API_URL || '' // Empty string = same-origin
const AVAILABLE_ICONS = [ const AVAILABLE_ICONS = [
'📝', '💼', '🏠', '🛒', '🎯', '💪', '📚', '✈️', '📝', '💼', '🏠', '🛒', '🎯', '💪', '📚', '✈️',

View File

@ -1,7 +1,8 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import './Auth.css' import './Auth.css'
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' // Use relative URLs to leverage Vite proxy (same-origin = cookies work!)
const API_URL = import.meta.env.VITE_API_URL || '' // Empty string = same-origin
function Auth({ onLogin }) { function Auth({ onLogin }) {
const [isLogin, setIsLogin] = useState(true) const [isLogin, setIsLogin] = useState(true)
@ -94,21 +95,11 @@ function Auth({ onLogin }) {
setLoading(true) setLoading(true)
setError('') setError('')
try { // CORRECT OAuth flow: Direct browser redirect to /auth/google
const response = await fetch(`${API_URL}/auth/google/url`) // This allows the backend to set session cookie and redirect to Google
const data = await response.json() // DO NOT use fetch() - OAuth requires browser redirects to preserve cookies
console.log('Redirecting to Google OAuth...')
if (data.url) { window.location.href = `${API_URL}/auth/google`
window.location.href = data.url
} else {
setError('Failed to initiate Google login')
setLoading(false)
}
} catch (error) {
setError('Failed to connect to Google login')
setLoading(false)
console.error('Google login error:', error)
}
} }
return ( return (

View File

@ -4,4 +4,34 @@ import react from '@vitejs/plugin-react'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: {
proxy: {
// Proxy API requests to backend - enables same-origin for session cookies
'/auth': {
target: 'http://localhost:8000',
changeOrigin: true,
secure: false,
},
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
secure: false,
},
'/login': {
target: 'http://localhost:8000',
changeOrigin: true,
secure: false,
},
'/register': {
target: 'http://localhost:8000',
changeOrigin: true,
secure: false,
},
'/logout': {
target: 'http://localhost:8000',
changeOrigin: true,
secure: false,
},
},
},
}) })