casualroom/apps/fenghuo/web/app/[locale]/auth/callback/back.tsx

143 lines
6.0 KiB
TypeScript
Raw Normal View History

2025-07-28 07:50:50 +08:00
'use client';
import { useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { oidcClient } from '@/lib/auth/oidc-client';
import { TokenManager } from '@/lib/auth/token-manager';
import { useAuth } from '@/components/providers/auth-provider';
import { useToken } from '@/components/providers/token-provider';
export default function AuthCallbackPage() {
const router = useRouter();
const searchParams = useSearchParams();
const { loadUser } = useAuth();
const { forceRefresh } = useToken();
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
const [message, setMessage] = useState('正在处理授权回调...');
useEffect(() => {
const handleCallback = async () => {
try {
// 获取 URL 参数
const code = searchParams.get('code');
const state = searchParams.get('state');
const error = searchParams.get('error');
// 检查是否有错误
if (error) {
throw new Error(searchParams.get('error_description') || error);
}
// 检查必需参数
if (!code || !state) {
throw new Error('授权回调参数不完整');
}
// 验证状态值防CSRF
const savedState = sessionStorage.getItem('oauth_state');
const savedRedirectUri = sessionStorage.getItem('oauth_redirect_uri');
if (state !== savedState) {
throw new Error('状态验证失败,可能存在安全风险');
}
if (!savedRedirectUri) {
throw new Error('未找到保存的回调地址');
}
setMessage('正在交换授权码...');
// 使用授权码获取令牌
const tokenResponse = await oidcClient.handleAuthorizationCallback(
code,
savedRedirectUri,
state
);
setMessage('正在保存令牌...');
// 保存令牌
TokenManager.saveTokens(tokenResponse);
setMessage('正在更新认证状态...');
// 同步令牌状态和认证状态
await forceRefresh();
await loadUser();
setMessage('登录成功,正在跳转...');
setStatus('success');
// 清理存储的状态
sessionStorage.removeItem('oauth_state');
sessionStorage.removeItem('oauth_redirect_uri');
// 延迟跳转,让用户看到成功信息
setTimeout(() => {
// 获取保存的返回URL如果没有则跳转到dashboard
const savedReturnUrl = sessionStorage.getItem('oauth_return_url') || '/dashboard';
sessionStorage.removeItem('oauth_return_url'); // 清理
router.push(savedReturnUrl);
}, 1500);
} catch (error) {
console.error('授权回调失败:', error);
setStatus('error');
setMessage(error instanceof Error ? error.message : '授权失败');
// 清理存储的状态
sessionStorage.removeItem('oauth_state');
sessionStorage.removeItem('oauth_redirect_uri');
}
};
handleCallback();
}, [searchParams, router, loadUser, forceRefresh]);
return (
<div className="flex items-center justify-center">
<div className="max-w-md w-full space-y-8">
<div className="text-center">
{status === 'loading' && (
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-500 mx-auto mb-4"></div>
)}
{status === 'success' && (
<div className="rounded-full h-12 w-12 bg-green-100 mx-auto mb-4 flex items-center justify-center">
<svg className="h-6 w-6 text-green-600" 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 mx-auto mb-4 flex items-center justify-center">
<svg className="h-6 w-6 text-red-600" 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>
)}
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
{status === 'loading' && '处理中'}
{status === 'success' && '登录成功'}
{status === 'error' && '登录失败'}
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
{message}
</p>
{status === 'error' && (
<button
onClick={() => router.push('/auth/login')}
className="mt-4 w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
</button>
)}
</div>
</div>
</div>
);
}