This commit is contained in:
MarkLo 2025-12-13 06:34:48 +08:00
parent 4eed994fcb
commit 2a65c7f44d
2 changed files with 90 additions and 23 deletions

View File

@ -15,10 +15,15 @@ from backend.app.services.auth_utils import create_access_token
router = APIRouter(prefix="/api/auth", tags=["Authentication"])
# Google OAuth Configuration
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID", "")
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET", "")
FRONTEND_URL = os.getenv("FRONTEND_URL", "http://localhost:3000")
# Google OAuth Configuration - read at request time for dynamic updates
def get_google_client_id():
return os.getenv("GOOGLE_CLIENT_ID", "")
def get_google_client_secret():
return os.getenv("GOOGLE_CLIENT_SECRET", "")
def get_frontend_url():
return os.getenv("FRONTEND_URL", "http://localhost:3000")
# Google OAuth URLs
GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
@ -31,17 +36,20 @@ async def google_login():
"""
Redirect to Google OAuth login page
"""
if not GOOGLE_CLIENT_ID:
client_id = get_google_client_id()
frontend_url = get_frontend_url()
if not client_id:
raise HTTPException(status_code=500, detail="Google OAuth not configured")
# Build the authorization URL
redirect_uri = f"{FRONTEND_URL}/api/auth/callback/google"
redirect_uri = f"{frontend_url}/api/auth/callback/google"
# For backend-handled callback (alternative):
# redirect_uri = f"{os.getenv('BACKEND_URL', 'http://localhost:8000')}/api/auth/google/callback"
params = {
"client_id": GOOGLE_CLIENT_ID,
"client_id": client_id,
"redirect_uri": redirect_uri,
"response_type": "code",
"scope": "openid email profile",
@ -64,7 +72,11 @@ async def google_callback(
Handle Google OAuth callback (backend-handled flow)
Exchange authorization code for tokens and create/update user
"""
if not GOOGLE_CLIENT_ID or not GOOGLE_CLIENT_SECRET:
client_id = get_google_client_id()
client_secret = get_google_client_secret()
frontend_url = get_frontend_url()
if not client_id or not client_secret:
raise HTTPException(status_code=500, detail="Google OAuth not configured")
# Determine redirect URI (must match what was used in the login request)
@ -76,8 +88,8 @@ async def google_callback(
GOOGLE_TOKEN_URL,
data={
"code": code,
"client_id": GOOGLE_CLIENT_ID,
"client_secret": GOOGLE_CLIENT_SECRET,
"client_id": client_id,
"client_secret": client_secret,
"redirect_uri": redirect_uri,
"grant_type": "authorization_code",
}
@ -143,7 +155,7 @@ async def google_callback(
})
# Redirect to frontend with token
redirect_url = f"{FRONTEND_URL}/auth/callback?token={jwt_token}"
redirect_url = f"{frontend_url}/auth/callback?token={jwt_token}"
return RedirectResponse(url=redirect_url)
@ -157,7 +169,10 @@ async def exchange_google_token(
Exchange Google authorization code for JWT token (frontend-handled flow)
This is called by the frontend after receiving the code from Google
"""
if not GOOGLE_CLIENT_ID or not GOOGLE_CLIENT_SECRET:
client_id = get_google_client_id()
client_secret = get_google_client_secret()
if not client_id or not client_secret:
raise HTTPException(status_code=500, detail="Google OAuth not configured")
# Exchange code for tokens
@ -166,8 +181,8 @@ async def exchange_google_token(
GOOGLE_TOKEN_URL,
data={
"code": code,
"client_id": GOOGLE_CLIENT_ID,
"client_secret": GOOGLE_CLIENT_SECRET,
"client_id": client_id,
"client_secret": client_secret,
"redirect_uri": redirect_uri,
"grant_type": "authorization_code",
}

View File

@ -1,40 +1,48 @@
/**
* Header component
* Header component with mobile-responsive design
*/
"use client";
import { useState } from "react";
import Link from "next/link";
import { Menu, X } from "lucide-react";
import { ThemeToggle } from "@/components/theme/ThemeToggle";
import { ApiSettingsDialog } from "@/components/settings/ApiSettingsDialog";
import { LoginButton } from "@/components/auth/login-button";
import { Button } from "@/components/ui/button";
export function Header() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<header className="border-b bg-gradient-to-r from-blue-500 to-pink-500 dark:from-blue-600 dark:to-purple-600 text-white">
<div className="container mx-auto px-4 py-6">
<div className="container mx-auto px-4 py-4 md:py-6">
<div className="flex items-center justify-between">
<Link href="/" className="flex items-center gap-2">
<div className="text-3xl font-bold">TradingAgentsX</div>
<div className="hidden md:block text-sm font-light opacity-90">
{/* Logo */}
<Link href="/" className="flex items-center gap-2 shrink-0">
<div className="text-xl md:text-3xl font-bold">TradingAgentsX</div>
<div className="hidden lg:block text-sm font-light opacity-90">
LLM
</div>
</Link>
<nav className="flex gap-6 items-center">
{/* Desktop Navigation */}
<nav className="hidden md:flex gap-4 lg:gap-6 items-center">
<Link
href="/"
className="hover:opacity-80 transition-opacity font-medium"
className="hover:opacity-80 transition-opacity font-medium text-sm lg:text-base"
>
</Link>
<Link
href="/analysis"
className="hover:opacity-80 transition-opacity font-medium"
className="hover:opacity-80 transition-opacity font-medium text-sm lg:text-base"
>
</Link>
<Link
href="/history"
className="hover:opacity-80 transition-opacity font-medium"
className="hover:opacity-80 transition-opacity font-medium text-sm lg:text-base"
>
</Link>
@ -42,7 +50,51 @@ export function Header() {
<ThemeToggle />
<LoginButton />
</nav>
{/* Mobile Menu Button */}
<div className="flex md:hidden items-center gap-2">
<ThemeToggle />
<Button
variant="ghost"
size="icon"
className="text-white hover:bg-white/20"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
{mobileMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
</Button>
</div>
</div>
{/* Mobile Navigation */}
{mobileMenuOpen && (
<nav className="md:hidden mt-4 pt-4 border-t border-white/20 space-y-3">
<Link
href="/"
className="block py-2 hover:opacity-80 transition-opacity font-medium"
onClick={() => setMobileMenuOpen(false)}
>
</Link>
<Link
href="/analysis"
className="block py-2 hover:opacity-80 transition-opacity font-medium"
onClick={() => setMobileMenuOpen(false)}
>
</Link>
<Link
href="/history"
className="block py-2 hover:opacity-80 transition-opacity font-medium"
onClick={() => setMobileMenuOpen(false)}
>
</Link>
<div className="flex items-center gap-3 pt-2">
<ApiSettingsDialog />
<LoginButton />
</div>
</nav>
)}
</div>
</header>
);