Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 116 additions & 22 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { handlers } from "@/app/auth"
import { NextResponse } from 'next/server'
import { NextRequest } from 'next/server'
import { getProxyConfig } from "@/proxy-config"
import { getProxyConfig, disableProxy } from "@/proxy-config"
import { HttpsProxyAgent } from "https-proxy-agent"

const TIMEOUT_DURATION = 60000;
Expand All @@ -12,24 +12,63 @@ interface ExtendedRequestInit extends RequestInit {
timeout?: number;
}

// 定义超时 Promise 的类型
type TimeoutPromise = Promise<never>

export async function GET(request: NextRequest): Promise<Response> {
try {
const { isEnabled, httpsAgent } = getProxyConfig();
const nextauthUrl = process.env.NEXTAUTH_URL || '';

// 检查是否是回调请求
if (request.url.includes('/api/auth/callback')) {
console.log('Callback request detected:', request.url);

// 处理请求
const response = await handlers.GET(request);

// 如果是成功的回调(通常是 302 重定向)
if (response instanceof Response && response.status === 302) {
const location = response.headers.get('location');

// 检查重定向 URL 是否匹配 NEXTAUTH_URL
if (location && (
location === nextauthUrl ||
location.startsWith(nextauthUrl + '/') ||
location === '/' ||
!location.includes('/api/auth')
)) {
console.log('Redirecting to app URL, disabling proxy:', location);
// 在响应发送后禁用代理
setTimeout(() => {
disableProxy();
}, 100);
}
}

return response instanceof Response ? response : NextResponse.json(response);
}

// 只在认证请求时使用代理
// 处理其他认证请求
if (isEnabled && request.url.includes('/api/auth')) {
console.log('originalFetch!!!!!!!!!!:', global.fetch);
console.log('Auth request with proxy:', request.url);
const originalFetch = global.fetch;

global.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
const extendedInit: ExtendedRequestInit = {
...init,
agent: httpsAgent,
timeout: TIMEOUT_DURATION,
};
return originalFetch(input, extendedInit);
const inputStr = typeof input === 'string' ? input : input.toString();

// 只对 GitHub OAuth 相关 URL 使用代理
if (inputStr.includes('github.com/login/oauth') ||
inputStr.includes('api.github.com/user')) {

console.log('Using proxy for fetch request:', inputStr);
const extendedInit: ExtendedRequestInit = {
...init,
agent: httpsAgent,
timeout: TIMEOUT_DURATION,
};
return originalFetch(input, extendedInit);
} else {
// 其他请求不使用代理
return originalFetch(input, init);
}
};

try {
Expand All @@ -40,7 +79,7 @@ export async function GET(request: NextRequest): Promise<Response> {
global.fetch = originalFetch;
}
} else {
// 非认证请求使用普通 fetch
// 非认证请求或代理未启用
const response = await handlers.GET(request);
return response instanceof Response ? response : NextResponse.json(response);
}
Expand All @@ -59,17 +98,72 @@ export async function GET(request: NextRequest): Promise<Response> {

export async function POST(request: NextRequest): Promise<Response> {
try {
const { isEnabled } = getProxyConfig();

if (isEnabled) {
// 使用超时机制
const timeoutPromise: TimeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), TIMEOUT_DURATION)
);
const response = await Promise.race([handlers.POST(request), timeoutPromise]);
const { isEnabled, httpsAgent } = getProxyConfig();
const nextauthUrl = process.env.NEXTAUTH_URL || '';

// 检查是否是回调请求
if (request.url.includes('/api/auth/callback')) {
console.log('Callback POST request detected:', request.url);

// 处理请求
const response = await handlers.POST(request);

// 如果是成功的回调(通常是 302 重定向)
if (response instanceof Response && response.status === 302) {
const location = response.headers.get('location');

// 检查重定向 URL 是否匹配 NEXTAUTH_URL
if (location && (
location === nextauthUrl ||
location.startsWith(nextauthUrl + '/') ||
location === '/' ||
!location.includes('/api/auth')
)) {
console.log('POST: Redirecting to app URL, disabling proxy:', location);
// 在响应发送后禁用代理
setTimeout(() => {
disableProxy();
}, 100);
}
}

return response instanceof Response ? response : NextResponse.json(response);
}

// 处理其他认证请求
if (isEnabled && request.url.includes('/api/auth')) {
console.log('Auth POST request with proxy:', request.url);
const originalFetch = global.fetch;

global.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
const inputStr = typeof input === 'string' ? input : input.toString();

// 只对 GitHub OAuth 相关 URL 使用代理
if (inputStr.includes('github.com/login/oauth') ||
inputStr.includes('api.github.com/user')) {

console.log('Using proxy for POST fetch request:', inputStr);
const extendedInit: ExtendedRequestInit = {
...init,
agent: httpsAgent,
timeout: TIMEOUT_DURATION,
};
return originalFetch(input, extendedInit);
} else {
// 其他请求不使用代理
return originalFetch(input, init);
}
};

try {
const response = await handlers.POST(request);
return response instanceof Response ? response : NextResponse.json(response);
} finally {
// 请求完成后恢复原始的 fetch
global.fetch = originalFetch;
}
} else {
// 不使用超时机制
// 非认证请求或代理未启用
const response = await handlers.POST(request);
return response instanceof Response ? response : NextResponse.json(response);
}
Expand Down
17 changes: 17 additions & 0 deletions app/api/auth/enable-proxy/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NextResponse } from 'next/server';
import { enableProxy } from "@/proxy-config";

export async function POST() {
try {
const config = enableProxy();
return NextResponse.json({
success: true,
proxyEnabled: config.isEnabled
});
} catch (error) {
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
}, { status: 500 });
}
}
40 changes: 40 additions & 0 deletions app/api/proxy-image/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { NextRequest, NextResponse } from 'next/server';
import { getProxyConfig } from '@/proxy-config';

export async function GET(request: NextRequest) {
try {
const url = request.nextUrl.searchParams.get('url');

if (!url) {
return new NextResponse('Missing URL parameter', { status: 400 });
}

const { isEnabled, httpsAgent } = getProxyConfig();

const fetchOptions: RequestInit = {};
if (isEnabled && url.includes('githubusercontent.com')) {
// 使用 @ts-expect-error 代替 @ts-ignore
// @ts-expect-error - agent 属性在浏览器端 RequestInit 类型中不存在
fetchOptions.agent = httpsAgent;
}

const response = await fetch(url, fetchOptions);

if (!response.ok) {
return new NextResponse('Failed to fetch image', { status: response.status });
}

const buffer = await response.arrayBuffer();
const headers = new Headers();
headers.set('Content-Type', response.headers.get('Content-Type') || 'image/jpeg');
headers.set('Cache-Control', 'public, max-age=86400');

return new NextResponse(buffer, {
status: 200,
headers
});
} catch (error) {
console.error('Error proxying image:', error);
return new NextResponse('Error fetching image', { status: 500 });
}
}
29 changes: 17 additions & 12 deletions app/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
// import { getProxyConfig } from "@/proxy-config"
import { setupGlobalFetch } from "@/proxy-config"

// 设置全局 fetch 拦截器(但不启用代理)
setupGlobalFetch();

// 打印环境变量(开发环境调试用)
console.log('Environment Config:', {
Expand Down Expand Up @@ -31,32 +34,34 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
debug: true,
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
console.log('Sign in attempt:', { user, account, profile, email, credentials });

console.log('signIn!!!!!!!!!!:', user, account, profile, email, credentials);
console.log('Sign in callback executed');
return true;
},
async redirect({ url, baseUrl }) {
console.log('Redirect:', { url, baseUrl });

console.log('redirect!!!!!!!!!!:', url, baseUrl);
return baseUrl;
},
async session({ session, user, token }) {
console.log('Session:', { session, user, token });
console.log('session!!!!!!!!!!:', session, user, token);
return session;
},
async jwt({ token, user, account, profile }) {
console.log('JWT:', { token, user, account, profile });
console.log('jwt!!!!!!!!!!:', token, user, account, profile);
return token;
}
},
events: {
async signIn(message) { console.log('signIn:', message) },
async signOut(message) { console.log('signOut:', message) },
async signIn(message) {
console.log('signIn event:', message);
},
async signOut(message) {
console.log('signOut:', message);
},
},
pages: {
signIn: '/auth/signin',
error: '/auth/error',
}
})



})
48 changes: 42 additions & 6 deletions components/sign-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useEffect } from 'react';
import { signIn, signOut, useSession } from 'next-auth/react';
import Image from 'next/image';
// import Image from 'next/image';
import Link from 'next/link';
import { Dropdown } from 'antd';
import type { MenuProps } from 'antd';
Expand Down Expand Up @@ -32,6 +32,36 @@ export default function SignInButton() {
}
}, [session]); // 依赖于 session

// 处理 GitHub 登录
const handleGitHubSignIn = async () => {
try {
// 先启用代理
await fetch('/api/auth/enable-proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});

console.log('已启用代理用于 GitHub 登录');

// 然后进行 GitHub 登录
await signIn("github");
} catch (error) {
console.error('登录过程中出错:', error);
}
};

// 在组件内部添加一个函数来处理图片 URL
// const getProxiedImageUrl = (imageUrl: string) => {
// if (!imageUrl) return '';
// // 检查是否是 GitHub 头像
// if (imageUrl.includes('githubusercontent.com')) {
// return `/api/proxy-image?url=${encodeURIComponent(imageUrl)}`;
// }
// return imageUrl;
// };

if (status === 'loading') {
return (
<button className="bg-gray-200 text-gray-400 px-4 py-2 rounded-md cursor-not-allowed">
Expand All @@ -56,8 +86,17 @@ export default function SignInButton() {
<Dropdown menu={{ items }} placement="bottomRight">
<div className="flex items-center gap-2 cursor-pointer">
<div className="flex items-center gap-2 px-2 py-1 rounded-full bg-gray-100 hover:bg-gray-200 transition-colors duration-200">
{session.user?.image && (
{/* {session.user?.image && (
<Image
src={getProxiedImageUrl(session.user.image)}
alt={session.user.name || 'User avatar'}
width={32}
height={32}
className="rounded-full"
/>
)} */}
{session.user?.image && (
<img
src={session.user.image}
alt={session.user.name || 'User avatar'}
width={32}
Expand All @@ -74,10 +113,7 @@ export default function SignInButton() {

return (
<button
onClick={async () => {

await signIn("github")
}}
onClick={handleGitHubSignIn}
className="flex items-center gap-2 px-4 py-2 bg-gray-800 hover:bg-gray-700 text-white rounded-md transition-colors duration-200"
>
<svg
Expand Down
Loading