2025-09-21 12:37:34 +08:00

286 lines
8.7 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.

import { NextRequest, NextResponse } from 'next/server';
import type { OAuthCallbackParams } from '@/app/types/google-oauth';
/**
* Google OAuth回调处理API
* 处理从Google OAuth返回的授权码完成用户认证
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { code, state, inviteCode } = body;
// 验证必需参数
if (!code || !state) {
return NextResponse.json(
{
success: false,
message: 'Missing required parameters: code and state'
},
{ status: 400 }
);
}
// 开发模式使用测试环境的OAuth处理
const isDevelopment = process.env.NODE_ENV === 'development';
const useTestEnv = isDevelopment; // 开发环境默认使用测试环境
if (useTestEnv) {
console.log('🧪 开发模式使用模拟OAuth响应');
// 解析state参数获取origin信息
let stateData: any = {};
try {
stateData = JSON.parse(state);
} catch (e) {
console.warn('无法解析state参数:', state);
}
// 模拟成功的OAuth响应
const mockResponse = {
success: true,
data: {
token: 'dev-mock-token-' + Date.now(),
user: {
userId: 'dev-user-' + Math.random().toString(36).substr(2, 9),
userName: 'Development User',
name: 'Dev User',
email: 'dev@movieflow.com',
authType: 'GOOGLE',
isNewUser: false
},
userInfo: {
userId: 'dev-user-' + Math.random().toString(36).substr(2, 9),
userName: 'Development User',
name: 'Dev User',
email: 'dev@movieflow.com',
authType: 'GOOGLE',
isNewUser: false
},
message: 'Development mode - Google authentication simulated'
}
};
console.log('返回模拟OAuth响应:', mockResponse);
return NextResponse.json(mockResponse);
}
// 解析state参数
let stateData: any = {};
try {
stateData = JSON.parse(state);
} catch (e) {
console.warn('无法解析state参数:', state);
return NextResponse.json(
{
success: false,
message: 'Invalid state parameter'
},
{ status: 400 }
);
}
console.log('Google OAuth回调处理开始:', {
codeLength: code.length,
stateData,
inviteCode
});
// 第一步使用authorization code向Google换取access token和id_token
const googleClientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || '847079918888-o1nne8d3ij80dn20qurivo987pv07225.apps.googleusercontent.com';
const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET || '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';
if (!googleClientSecret) {
console.error('Google Client Secret未配置');
return NextResponse.json(
{
success: false,
message: 'Google Client Secret not configured'
},
{ status: 500 }
);
}
console.log('开始向Google交换token...');
// 创建fetch配置包含超时和重试机制
const fetchOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
code,
client_id: googleClientId,
client_secret: googleClientSecret,
redirect_uri: getRedirectUri(request),
grant_type: 'authorization_code',
}),
// 增加超时时间到30秒
signal: AbortSignal.timeout(30000)
};
let tokenResponse;
try {
tokenResponse = await fetch('https://oauth2.googleapis.com/token', fetchOptions);
} catch (fetchError: any) {
console.error('Google API连接失败:', fetchError.message);
// 如果是超时错误,提供更友好的错误信息
if (fetchError.name === 'TimeoutError' || fetchError.code === 'UND_ERR_CONNECT_TIMEOUT') {
return NextResponse.json(
{
success: false,
message: 'Google authentication service is temporarily unavailable. Please check your network connection and try again.'
},
{ status: 503 }
);
}
throw fetchError;
}
console.log('Google token exchange响应状态:', tokenResponse.status);
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text();
console.error('Google token exchange failed:', errorText);
return NextResponse.json(
{
success: false,
message: 'Failed to exchange authorization code for tokens'
},
{ status: 400 }
);
}
const tokenData = await tokenResponse.json();
console.log('Google token exchange成功获得token');
const { id_token } = tokenData;
if (!id_token) {
console.error('No id_token received from Google');
return NextResponse.json(
{
success: false,
message: 'No id_token received from Google'
},
{ status: 400 }
);
}
// 第二步使用id_token调用Java后端
const javaBaseUrl = process.env.NEXT_PUBLIC_JAVA_URL || 'https://77.app.java.auth.qikongjian.com';
console.log('开始调用Java后端:', javaBaseUrl);
const backendResponse = await fetch(`${javaBaseUrl}/api/auth/google/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
idToken: id_token, // 使用从Google获取的id_token
action: 'auto', // 自动判断登录或注册
inviteCode: inviteCode || stateData.inviteCode || undefined
})
});
console.log('Java后端响应状态:', backendResponse.status);
const backendResult = await backendResponse.json();
if (!backendResponse.ok || !backendResult.success) {
console.error('Java后端处理Google OAuth失败:', backendResult);
return NextResponse.json(
{
success: false,
message: backendResult.message || 'Google authentication failed'
},
{ status: backendResponse.status || 500 }
);
}
console.log('Google OAuth认证成功:', {
userId: backendResult.data?.user?.userId,
email: backendResult.data?.user?.email
});
// 返回成功结果
return NextResponse.json({
success: true,
data: {
token: backendResult.data.token,
user: backendResult.data.userInfo || backendResult.data.user,
message: 'Google authentication successful'
}
});
} catch (error: any) {
console.error('Google OAuth回调处理错误:', error);
// 检查是否是网络连接错误
if (error.code === 'ECONNREFUSED' || error.message?.includes('fetch failed')) {
console.error('网络连接失败可能是Java后端服务不可用');
return NextResponse.json(
{
success: false,
message: 'Backend service unavailable. Please try again later.'
},
{ status: 503 }
);
}
return NextResponse.json(
{
success: false,
message: error.message || 'Internal server error during Google OAuth callback'
},
{ status: 500 }
);
}
}
/**
* 根据请求获取正确的redirect_uri
*/
function getRedirectUri(request: NextRequest): string {
const host = request.headers.get('host') || '';
if (host.includes('localhost') || host.includes('127.0.0.1')) {
// 本地开发环境使用实际的端口号可能是3000或3001
const protocol = 'http';
return `${protocol}://${host}/api/auth/google/callback`;
} else if (host.includes('movieflow.net')) {
return 'https://www.movieflow.net/api/auth/google/callback';
} else if (host.includes('movieflow.ai')) {
return 'https://www.movieflow.ai/api/auth/google/callback';
} else {
// 默认使用生产环境
return 'https://www.movieflow.ai/api/auth/google/callback';
}
}
/**
* 处理GET请求 - Google OAuth回调
* 将GET请求重定向到页面路由进行处理
*/
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const code = searchParams.get('code');
const state = searchParams.get('state');
if (!code || !state) {
return NextResponse.json(
{
success: false,
message: 'Missing required parameters: code and state'
},
{ status: 400 }
);
}
// 重定向到页面路由让页面处理OAuth回调
const callbackUrl = `/users/oauth/callback?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`;
return NextResponse.redirect(new URL(callbackUrl, request.url));
}