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

143 lines
6.0 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 { 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>
);
}