This commit is contained in:
parent
4eed994fcb
commit
2a65c7f44d
|
|
@ -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",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue