Fix google auth
This commit is contained in:
parent
941d005d6e
commit
e48a5a2d2c
104
backend/main.py
104
backend/main.py
@ -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")
|
||||||
|
|||||||
@ -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 = [
|
||||||
'📝', '💼', '🏠', '🛒', '🎯', '💪', '📚', '✈️',
|
'📝', '💼', '🏠', '🛒', '🎯', '💪', '📚', '✈️',
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user