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

View File

@ -1,40 +1,48 @@
/** /**
* Header component * Header component with mobile-responsive design
*/ */
"use client"; "use client";
import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { Menu, X } from "lucide-react";
import { ThemeToggle } from "@/components/theme/ThemeToggle"; import { ThemeToggle } from "@/components/theme/ThemeToggle";
import { ApiSettingsDialog } from "@/components/settings/ApiSettingsDialog"; import { ApiSettingsDialog } from "@/components/settings/ApiSettingsDialog";
import { LoginButton } from "@/components/auth/login-button"; import { LoginButton } from "@/components/auth/login-button";
import { Button } from "@/components/ui/button";
export function Header() { export function Header() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return ( 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"> <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"> <div className="flex items-center justify-between">
<Link href="/" className="flex items-center gap-2"> {/* Logo */}
<div className="text-3xl font-bold">TradingAgentsX</div> <Link href="/" className="flex items-center gap-2 shrink-0">
<div className="hidden md:block text-sm font-light opacity-90"> <div className="text-xl md:text-3xl font-bold">TradingAgentsX</div>
<div className="hidden lg:block text-sm font-light opacity-90">
LLM LLM
</div> </div>
</Link> </Link>
<nav className="flex gap-6 items-center">
{/* Desktop Navigation */}
<nav className="hidden md:flex gap-4 lg:gap-6 items-center">
<Link <Link
href="/" href="/"
className="hover:opacity-80 transition-opacity font-medium" className="hover:opacity-80 transition-opacity font-medium text-sm lg:text-base"
> >
</Link> </Link>
<Link <Link
href="/analysis" 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>
<Link <Link
href="/history" href="/history"
className="hover:opacity-80 transition-opacity font-medium" className="hover:opacity-80 transition-opacity font-medium text-sm lg:text-base"
> >
</Link> </Link>
@ -42,7 +50,51 @@ export function Header() {
<ThemeToggle /> <ThemeToggle />
<LoginButton /> <LoginButton />
</nav> </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> </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> </div>
</header> </header>
); );