casualroom/apps/fenghuo/web/app/[locale]/auth/better-auth-callback/page.tsx

218 lines
9.9 KiB
TypeScript
Executable File
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 { 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 { client } from "@/lib/auth-client";
export default function BetterAuthCallbackPage() {
const router = useRouter();
const searchParams = useSearchParams();
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
const [message, setMessage] = useState('正在处理认证回调...');
const [authData, setAuthData] = useState<any>(null);
const [session, setSession] = useState<any>(null);
useEffect(() => {
const handleCallback = async () => {
try {
console.log('开始处理 Better Auth 回调...');
console.log('当前 URL:', window.location.href);
// 获取URL参数
const code = searchParams.get('code');
const error = searchParams.get('error');
const errorDescription = searchParams.get('error_description');
const state = searchParams.get('state');
const sessionState = searchParams.get('session_state');
console.log('回调参数:', {
code,
error,
errorDescription,
state,
sessionState,
fullURL: window.location.href
});
// 检查是否有错误
if (error) {
let errorMsg = error;
if (error === 'oauth_code_verification_failed') {
errorMsg = 'OAuth授权码验证失败。可能的原因包括\n1. 授权码已过期\n2. 授权码已被使用\n3. 客户端配置不匹配\n4. PKCE验证失败';
} else if (error === 'account_not_linked') {
errorMsg = '账户未关联。请先创建用户账户或登录现有账户。';
}
throw new Error(errorDescription || errorMsg);
}
// 如果没有授权码,说明可能是直接访问回调页面
if (!code) {
throw new Error('未收到授权码。请从测试页面启动 OAuth 流程。');
}
setMessage('收到授权码,正在完成认证流程...');
// 等待 Better Auth 处理回调
// Better Auth 会自动处理从 URL 参数中的授权码交换令牌
let attempts = 0;
const maxAttempts = 10;
const checkSession = async (): Promise<any> => {
attempts++;
console.log(`尝试检查会话,第 ${attempts}`);
try {
const sessionResult = await client.getSession();
console.log('会话检查结果:', sessionResult);
if (sessionResult.data) {
return sessionResult.data;
} else if (attempts < maxAttempts) {
// 等待一段时间再重试
await new Promise(resolve => setTimeout(resolve, 500));
return checkSession();
} else {
throw new Error('认证完成但未建立会话');
}
} catch (error) {
console.error(`会话检查失败 (尝试 ${attempts}):`, error);
if (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 500));
return checkSession();
} else {
throw error;
}
}
};
const finalSession = await checkSession();
setStatus('success');
setMessage('OAuth2 认证成功!已建立用户会话。');
setSession(finalSession);
setAuthData({
code,
state,
sessionState,
timestamp: new Date().toISOString(),
user: finalSession.user
});
toast.success('OAuth2 认证成功!');
} catch (error) {
console.error('认证回调失败:', error);
setStatus('error');
setMessage(error instanceof Error ? error.message : '认证失败');
toast.error(`认证失败: ${error instanceof Error ? error.message : '未知错误'}`);
}
};
// 给页面一点时间加载,然后处理回调
const timeoutId = setTimeout(handleCallback, 100);
return () => clearTimeout(timeoutId);
}, [searchParams]);
const handleBackToTest = () => {
router.push('/test');
};
const handleTryAgain = () => {
router.push('/test');
};
return (
<div className="container mx-auto p-6 max-w-2xl">
<Card>
<CardHeader className="text-center">
<div className="mx-auto mb-4">
{status === 'loading' && (
<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 className="text-2xl">
{status === 'loading' && 'Better Auth 回调处理中'}
{status === 'success' && '认证成功'}
{status === 'error' && '认证失败'}
</CardTitle>
<CardDescription>
{message}
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{authData && (
<div className="space-y-4">
<h3 className="font-semibold">:</h3>
<div className="bg-muted p-4 rounded-md">
<pre className="text-xs overflow-x-auto">
{JSON.stringify(authData, null, 2)}
</pre>
</div>
</div>
)}
{session && (
<div className="space-y-4">
<h3 className="font-semibold">:</h3>
<div className="bg-green-50 dark:bg-green-900/20 p-4 rounded-md">
<div className="text-sm space-y-1">
<div><strong> ID:</strong> {session.user?.id}</div>
<div><strong>:</strong> {session.user?.email}</div>
<div><strong>:</strong> {session.user?.name}</div>
<div><strong> ID:</strong> {session.id}</div>
</div>
</div>
</div>
)}
<div className="flex flex-col gap-3">
{status === 'success' && (
<Button onClick={handleBackToTest} className="w-full">
</Button>
)}
{status === 'error' && (
<Button onClick={handleTryAgain} variant="outline" className="w-full">
</Button>
)}
</div>
<div className="text-sm text-muted-foreground">
<h4 className="font-semibold mb-2">:</h4>
<ul className="list-disc list-inside space-y-1">
<li> Better Auth Generic OAuth </li>
<li> OAuth2/OIDC </li>
<li></li>
<li>Better Auth </li>
<li> "account_not_linked" </li>
</ul>
</div>
</CardContent>
</Card>
</div>
);
}