217 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { CheckCircle, XCircle, Loader2, AlertTriangle } from "lucide-react";
import { loginWithGoogleToken } from "@/lib/auth";
import type { OAuthCallbackParams, OAuthState } from "@/app/types/google-oauth";
export default function OAuthCallback() {
const router = useRouter();
const searchParams = useSearchParams();
const [status, setStatus] = useState<"loading" | "success" | "error" | "conflict">("loading");
const [message, setMessage] = useState("");
const [conflictData, setConflictData] = useState<any>(null);
useEffect(() => {
const handleOAuthCallback = async () => {
try {
// 获取URL参数
const params: OAuthCallbackParams = {
code: searchParams.get("code") || undefined,
state: searchParams.get("state") || undefined,
error: searchParams.get("error") || undefined,
error_description: searchParams.get("error_description") || undefined,
};
// 检查是否有错误
if (params.error) {
setStatus("error");
setMessage(params.error_description || `OAuth error: ${params.error}`);
return;
}
// 验证必需参数
if (!params.code || !params.state) {
setStatus("error");
setMessage("Missing required OAuth parameters");
return;
}
// 验证state参数防止CSRF攻击
const savedStateStr = sessionStorage.getItem("google_oauth_state");
if (!savedStateStr) {
setStatus("error");
setMessage("OAuth state not found. Please try again.");
return;
}
const savedState: OAuthState = JSON.parse(savedStateStr);
if (savedState.state !== params.state) {
setStatus("error");
setMessage("Invalid OAuth state. Possible CSRF attack detected.");
return;
}
// 检查state是否过期5分钟
if (Date.now() - savedState.timestamp > 5 * 60 * 1000) {
setStatus("error");
setMessage("OAuth session expired. Please try again.");
return;
}
// 清除保存的state
sessionStorage.removeItem("google_oauth_state");
// 这里应该调用后端API处理授权码
// 但根据当前的实现我们需要使用Google JavaScript SDK
// 或者等待后端提供处理授权码的接口
// 临时处理:重定向到登录页面并显示消息
setStatus("error");
setMessage("OAuth callback processing is not yet implemented. Please use the Google login button directly.");
// 3秒后重定向到登录页面
setTimeout(() => {
router.push("/login?error=oauth_callback_incomplete");
}, 3000);
} catch (error: any) {
console.error("OAuth callback error:", error);
if (error.type === 'EMAIL_CONFLICT') {
setStatus("conflict");
setMessage(error.message);
setConflictData(error.data);
} else {
setStatus("error");
setMessage(error.message || "OAuth callback processing failed");
}
}
};
handleOAuthCallback();
}, [searchParams, router]);
const handleBindAccount = async () => {
try {
// 这里应该实现账户绑定逻辑
// 需要调用 /api/auth/google/bind 接口
console.log("Account binding not yet implemented");
setMessage("Account binding feature is not yet implemented");
} catch (error: any) {
console.error("Account binding failed:", error);
setMessage(error.message || "Account binding failed");
}
};
const handleReturnToLogin = () => {
router.push("/login");
};
const renderContent = () => {
switch (status) {
case "loading":
return (
<div className="flex flex-col items-center justify-center space-y-4">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-cyan-400 to-purple-600 rounded-full blur-lg opacity-30 animate-pulse"></div>
<Loader2 className="h-16 w-16 animate-spin text-cyan-400 relative z-10" />
</div>
<p className="text-lg text-gray-300">Processing OAuth callback...</p>
</div>
);
case "success":
return (
<div className="flex flex-col items-center justify-center space-y-4">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-cyan-400 to-purple-600 rounded-full blur-lg opacity-30"></div>
<CheckCircle className="h-16 w-16 text-cyan-400 relative z-10" />
</div>
<h2 className="text-2xl font-semibold bg-gradient-to-r from-cyan-400 to-purple-600 bg-clip-text text-transparent">
Login Successful
</h2>
<p className="text-gray-300 text-center max-w-md">{message}</p>
</div>
);
case "conflict":
return (
<div className="flex flex-col items-center justify-center space-y-4">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-yellow-400 to-orange-500 rounded-full blur-lg opacity-30"></div>
<AlertTriangle className="h-16 w-16 text-yellow-400 relative z-10" />
</div>
<h2 className="text-2xl font-semibold bg-gradient-to-r from-yellow-400 to-orange-500 bg-clip-text text-transparent">
Account Conflict
</h2>
<p className="text-gray-300 text-center max-w-md">{message}</p>
{conflictData && (
<div className="text-center space-y-4">
<p className="text-sm text-gray-400">
Email: {conflictData.existingUser?.email}
</p>
<div className="flex gap-3">
<button
onClick={handleBindAccount}
className="px-4 py-2 bg-gradient-to-r from-cyan-400 to-purple-600 text-white rounded-lg hover:from-cyan-500 hover:to-purple-700 transition-all duration-300"
>
Bind Account
</button>
<button
onClick={handleReturnToLogin}
className="px-4 py-2 border border-white/20 text-white rounded-lg hover:bg-white/10 transition-all duration-300"
>
Return to Login
</button>
</div>
</div>
)}
</div>
);
case "error":
return (
<div className="flex flex-col items-center justify-center space-y-4">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-red-500 to-orange-500 rounded-full blur-lg opacity-30"></div>
<XCircle className="h-16 w-16 text-red-400 relative z-10" />
</div>
<h2 className="text-2xl font-semibold bg-gradient-to-r from-red-500 to-orange-500 bg-clip-text text-transparent">
OAuth Failed
</h2>
<p className="text-gray-300 text-center max-w-md">{message}</p>
<button
onClick={handleReturnToLogin}
className="mt-4 px-6 py-2 bg-gradient-to-r from-cyan-400 to-purple-600 text-white rounded-lg hover:from-cyan-500 hover:to-purple-700 transition-all duration-300 shadow-lg hover:shadow-xl transform hover:scale-105"
>
Return to Login
</button>
</div>
);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-black via-gray-900 to-black px-4">
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-sm rounded-2xl shadow-2xl p-8 max-w-md w-full border border-gray-700/50 relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-cyan-400/10 to-purple-600/10 pointer-events-none"></div>
<div className="relative z-10">
{status === "loading" && (
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-white mb-2 bg-gradient-to-r from-cyan-400 to-purple-600 bg-clip-text text-transparent">
OAuth Callback
</h1>
<p className="text-gray-300">
Please wait while we process your authentication
</p>
</div>
)}
{renderContent()}
</div>
</div>
</div>
);
}