172 lines
7.4 KiB
TypeScript
172 lines
7.4 KiB
TypeScript
|
|
"use client";
|
|||
|
|
|
|||
|
|
import { useEffect, useState } from "react";
|
|||
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|||
|
|
import { toast } from "@nice/ui/components/sonner";
|
|||
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@nice/ui/components/card";
|
|||
|
|
import { Button } from "@nice/ui/components/button";
|
|||
|
|
import { authClient } from "@/lib/auth-client";
|
|||
|
|
|
|||
|
|
export default function AuthCallbackPage() {
|
|||
|
|
const router = useRouter();
|
|||
|
|
const searchParams = useSearchParams();
|
|||
|
|
const [status, setStatus] = useState<'processing' | 'success' | 'error'>('processing');
|
|||
|
|
const [message, setMessage] = useState('正在处理OAuth认证...');
|
|||
|
|
const [userSession, setUserSession] = useState<any>(null);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const handleOAuthCallback = async () => {
|
|||
|
|
try {
|
|||
|
|
// 获取URL参数检查是否有错误
|
|||
|
|
const error = searchParams.get('error');
|
|||
|
|
const errorDescription = searchParams.get('error_description');
|
|||
|
|
const code = searchParams.get('code');
|
|||
|
|
|
|||
|
|
console.log('OAuth回调参数:', {
|
|||
|
|
error,
|
|||
|
|
errorDescription,
|
|||
|
|
code,
|
|||
|
|
url: window.location.href
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
throw new Error(errorDescription || `OAuth错误: ${error}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!code) {
|
|||
|
|
throw new Error('未收到授权码,请重新开始OAuth流程');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setMessage('收到授权码,正在验证会话...');
|
|||
|
|
|
|||
|
|
// Better Auth会自动处理OAuth回调,我们立即开始检查会话
|
|||
|
|
// 检查会话状态
|
|||
|
|
let attempts = 0;
|
|||
|
|
const maxAttempts = 10;
|
|||
|
|
|
|||
|
|
while (attempts < maxAttempts) {
|
|||
|
|
attempts++;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const session = await authClient.getSession();
|
|||
|
|
|
|||
|
|
if (session.data?.user) {
|
|||
|
|
setStatus('success');
|
|||
|
|
setMessage('OAuth认证成功!');
|
|||
|
|
setUserSession(session.data);
|
|||
|
|
toast.success('登录成功!');
|
|||
|
|
|
|||
|
|
// 智能重定向逻辑
|
|||
|
|
let redirectTo = '/';
|
|||
|
|
|
|||
|
|
// 延迟跳转
|
|||
|
|
setTimeout(() => {
|
|||
|
|
router.push(redirectTo);
|
|||
|
|
}, 1500);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.log(`会话检查失败 (尝试 ${attempts}):`, err);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果还没有会话,等待一段时间再重试
|
|||
|
|
if (attempts < maxAttempts) {
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
throw new Error('OAuth认证超时,未能建立用户会话');
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('OAuth回调处理失败:', error);
|
|||
|
|
setStatus('error');
|
|||
|
|
setMessage(error instanceof Error ? error.message : '认证失败');
|
|||
|
|
toast.error(`认证失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 延迟执行回调处理,确保页面已加载
|
|||
|
|
const timer = setTimeout(handleOAuthCallback, 100);
|
|||
|
|
return () => clearTimeout(timer);
|
|||
|
|
}, [searchParams, router]);
|
|||
|
|
|
|||
|
|
const handleRetry = () => {
|
|||
|
|
// 重新尝试时也使用相同的重定向逻辑
|
|||
|
|
const redirectTo = searchParams.get('state') || '/';
|
|||
|
|
router.push(redirectTo);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="container mx-auto p-6 max-w-lg">
|
|||
|
|
<Card>
|
|||
|
|
<CardHeader className="text-center">
|
|||
|
|
<div className="mx-auto mb-4">
|
|||
|
|
{status === 'processing' && (
|
|||
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto"></div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{status === 'success' && (
|
|||
|
|
<div className="rounded-full h-12 w-12 bg-green-100 dark:bg-green-900 mx-auto flex items-center justify-center">
|
|||
|
|
<svg className="h-6 w-6 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
|
|||
|
|
</svg>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{status === 'error' && (
|
|||
|
|
<div className="rounded-full h-12 w-12 bg-red-100 dark:bg-red-900 mx-auto flex items-center justify-center">
|
|||
|
|
<svg className="h-6 w-6 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12"></path>
|
|||
|
|
</svg>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<CardTitle>
|
|||
|
|
{status === 'processing' && 'OAuth认证中'}
|
|||
|
|
{status === 'success' && '认证成功'}
|
|||
|
|
{status === 'error' && '认证失败'}
|
|||
|
|
</CardTitle>
|
|||
|
|
|
|||
|
|
<CardDescription>
|
|||
|
|
{message}
|
|||
|
|
</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
|
|||
|
|
<CardContent className="space-y-4">
|
|||
|
|
{userSession && (
|
|||
|
|
<div className="bg-green-50 dark:bg-green-900/20 p-4 rounded-md">
|
|||
|
|
<h3 className="font-semibold mb-2">用户信息:</h3>
|
|||
|
|
<div className="text-sm space-y-1">
|
|||
|
|
<div><strong>邮箱:</strong> {userSession.user.email}</div>
|
|||
|
|
<div><strong>姓名:</strong> {userSession.user.name}</div>
|
|||
|
|
<div><strong>用户ID:</strong> {userSession.user.id}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{status === 'success' && (
|
|||
|
|
<div className="text-center text-sm text-muted-foreground">
|
|||
|
|
正在跳转回原页面...
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{status === 'error' && (
|
|||
|
|
<Button onClick={handleRetry} className="w-full">
|
|||
|
|
返回原页面
|
|||
|
|
</Button>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<div className="text-xs text-muted-foreground">
|
|||
|
|
<p className="mb-2"><strong>说明:</strong></p>
|
|||
|
|
<ul className="list-disc list-inside space-y-1">
|
|||
|
|
<li>此页面处理OAuth2/OIDC认证回调</li>
|
|||
|
|
<li>Better Auth自动处理令牌交换</li>
|
|||
|
|
<li>成功后将自动跳转回原页面</li>
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|