diff --git a/app/test/ip-debug/page.tsx b/app/test/ip-debug/page.tsx index f619a78..017b719 100644 --- a/app/test/ip-debug/page.tsx +++ b/app/test/ip-debug/page.tsx @@ -1,11 +1,10 @@ -'use client' +"use client" -import { useState, useEffect } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { Alert, AlertDescription } from '@/components/ui/alert' -import { Button } from '@/components/ui/button' -import { Globe, Info, AlertCircle, CheckCircle, RefreshCw } from 'lucide-react' +import { useEffect, useState } from "react" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { RefreshCw, Shield, AlertTriangle, CheckCircle } from "lucide-react" interface IpDebugInfo { ip: string @@ -13,20 +12,26 @@ interface IpDebugInfo { enableIpWhitelist: boolean allowedIps: string[] timestamp: string - debug: { - allIpSources: Record - allFoundIps: string[] - isLocalDevelopment: boolean - localIp: string | null - environment: string - host: string | null - referer: string | null - userAgent: string | null - originalDetectedIp: string - finalDetectedIp: string + debug?: { + allIpSources?: Record + allFoundIps?: string[] + isLocalDevelopment?: boolean + localIp?: string | null + environment?: string + host?: string + referer?: string + userAgent?: string + originalDetectedIp?: string + finalDetectedIp?: string + rawDetectedIp?: string + ipDetectionMethod?: string + } + ipv6Info?: { + isIPv6Mapped: boolean + originalFormat: string + ipv6Format: string + hasIPv6Support: boolean } - location: any - development: any } export default function IpDebugPage() { @@ -45,8 +50,8 @@ export default function IpDebugPage() { const data = await response.json() setIpInfo(data) } catch (error) { - console.error('Error fetching IP info:', error) - setError('無法獲取IP信息') + console.error("無法獲取IP信息:", error) + setError("無法獲取IP信息") } finally { setLoading(false) } @@ -58,10 +63,12 @@ export default function IpDebugPage() { if (loading) { return ( -
-
- -

載入中...

+
+
+
+ + 載入中... +
) @@ -69,199 +76,226 @@ export default function IpDebugPage() { if (error || !ipInfo) { return ( -
- - - {error} - +
+ + + + + 錯誤 + + + +

{error}

+ +
+
) } return ( -
-
-

IP 調試信息

-

詳細的IP檢測和調試信息

+
+
+

IP 檢測調試

+
{/* 主要IP信息 */} - - 檢測到的IP地址 + {ipInfo.enableIpWhitelist && !ipInfo.isAllowed ? ( + <> + + IP 被拒絕 + + ) : ( + <> + + IP 檢測正常 + + )} - - 系統檢測到的主要IP地址信息 -
-

最終檢測到的IP

- + +

{ipInfo.ip} - +

-

原始檢測到的IP

- - {ipInfo.debug.originalDetectedIp} - -
-
- -
-
-

IP白名單狀態

- - {ipInfo.isAllowed ? '允許' : '拒絕'} - -
-
-

白名單功能

- - {ipInfo.enableIpWhitelist ? '已啟用' : '已禁用'} - -
-
-

環境

- - {ipInfo.debug.environment} - -
-
-
-
- - {/* 所有找到的IP */} - - - 所有檢測到的IP地址 - - 從各種來源檢測到的所有IP地址 - - - -
- {ipInfo.debug.allFoundIps.length > 0 ? ( - ipInfo.debug.allFoundIps.map((ip, index) => ( -
- - {ip} - - {ip === ipInfo.ip && ( - - 最終選擇 - - )} -
- )) - ) : ( -

沒有檢測到任何IP地址

- )} -
-
-
- - {/* IP來源詳細信息 */} - - - IP來源詳細信息 - - 各種HTTP頭和連接信息中的IP地址 - - - -
- {Object.entries(ipInfo.debug.allIpSources).map(([source, value]) => ( -
- {source}: - - {value || '未設置'} - + +
+ + {ipInfo.isAllowed ? "允許" : "拒絕"} +
- ))} +
+ + {ipInfo.enableIpWhitelist && ( +
+ +
+ + 已啟用 +
+
+ )}
- {/* 本地開發環境信息 */} - {ipInfo.development && ( - - - -
{ipInfo.development.message}
-
- {ipInfo.development.suggestions.map((suggestion, index) => ( -
• {suggestion}
- ))} -
-
-
- )} - - {/* 地理位置信息 */} - {ipInfo.location && ( + {/* 詳細調試信息 */} + {ipInfo.debug && ( - 地理位置信息 - - IP地址的地理位置信息 - + 詳細調試信息 - -
-              {JSON.stringify(ipInfo.location, null, 2)}
-            
+ +
+
+ +

+ {ipInfo.debug.environment || '未知'} +

+
+
+ +

+ {ipInfo.debug.ipDetectionMethod || '未知'} +

+
+
+ + {ipInfo.debug.allFoundIps && ipInfo.debug.allFoundIps.length > 0 && ( +
+ +
+ {ipInfo.debug.allFoundIps.map((ip, index) => ( +
+ {ip} +
+ ))} +
+
+ )} + + {ipInfo.debug.allIpSources && ( +
+ +
+ {Object.entries(ipInfo.debug.allIpSources).map(([key, value]) => ( +
+ {key}: + {value || 'null'} +
+ ))} +
+
+ )}
)} - {/* 其他調試信息 */} - - - 其他調試信息 - - 額外的調試和環境信息 - - - -
+ {/* IPv6信息 */} + {ipInfo.ipv6Info && ( + + + IPv6 信息 + +
-

主機

-

{ipInfo.debug.host || '未設置'}

+ + + {ipInfo.ipv6Info.isIPv6Mapped ? "是" : "否"} +
-

引用來源

-

{ipInfo.debug.referer || '未設置'}

+ + + {ipInfo.ipv6Info.hasIPv6Support ? "已啟用" : "未啟用"} +
-
-

用戶代理

-

{ipInfo.debug.userAgent || '未設置'}

+ {ipInfo.ipv6Info.isIPv6Mapped && ( +
+ +

+ {ipInfo.ipv6Info.ipv6Format} +

+
+ )} + + + )} + + {/* 允許的IP列表 */} + {ipInfo.enableIpWhitelist && ipInfo.allowedIps.length > 0 && ( + + + 允許的IP列表 + + +
+ {ipInfo.allowedIps.map((ip, index) => ( +
+ + {ip === ipInfo.ip ? "當前" : ""} + + {ip} +
+ ))}
-
-

時間戳

-

{ipInfo.timestamp}

+ + + )} + + {/* 建議 */} + + + 建議 + + + {!ipInfo.enableIpWhitelist && ( +
+

+ ⚠️ IP白名單功能未啟用。如果您的IP檢測正常,建議啟用白名單功能以提高安全性。 +

+ )} + + {ipInfo.enableIpWhitelist && !ipInfo.isAllowed && ( +
+

+ ❌ 您的IP不在允許列表中。請聯繫管理員將您的IP添加到白名單。 +

+
+ )} + + {ipInfo.ip.startsWith('172.70.') && ( +
+

+ 💡 檢測到Cloudflare代理IP。系統已優化處理此類代理IP,會嘗試獲取您的真實IP。 +

+
+ )} + +
+

+ ✅ 如果IP檢測仍有問題,請檢查1Panel的反向代理配置,確保正確轉發客戶端IP頭部。 +

- - {/* 操作按鈕 */} -
- - -
) -} \ No newline at end of file +} \ No newline at end of file diff --git a/lib/ip-utils.ts b/lib/ip-utils.ts index 78fb480..29efebc 100644 --- a/lib/ip-utils.ts +++ b/lib/ip-utils.ts @@ -83,23 +83,27 @@ function cleanIpAddress(ip: string): string | null { export function getClientIp(req: any): string { // 按優先順序檢查各種IP來源 const ipSources = [ - // 代理伺服器轉發的IP + // 1Panel 和常見代理伺服器轉發的IP (優先級最高) req.headers['x-forwarded-for'], req.headers['x-real-ip'], req.headers['x-client-ip'], req.headers['cf-connecting-ip'], // Cloudflare + req.headers['x-original-forwarded-for'], // 某些代理伺服器 + req.headers['x-cluster-client-ip'], // 集群環境 + + // 其他代理頭 req.headers['x-forwarded'], // 舊版代理頭 req.headers['forwarded-for'], req.headers['forwarded'], + // 1Panel 特殊頭部 + req.headers['x-1panel-client-ip'], // 1Panel 可能使用的頭部 + req.headers['x-nginx-proxy-real-ip'], // Nginx 代理 + // 直接連接的IP req.connection?.remoteAddress, req.socket?.remoteAddress, req.ip, - - // Next.js 特定的IP來源 - req.headers['x-original-forwarded-for'], - req.headers['x-cluster-client-ip'], ]; // 收集所有找到的IP用於調試 @@ -115,8 +119,8 @@ export function getClientIp(req: any): string { const cleanIp = cleanIpAddress(ip); if (cleanIp) { foundIps.push(cleanIp); - // 檢查是否為公網IP - if (isPublicIp(cleanIp)) { + // 檢查是否為公網IP,排除內部IP和1Panel代理IP + if (isPublicIp(cleanIp) && !isInternalProxyIp(cleanIp)) { return cleanIp; } } @@ -187,6 +191,207 @@ function isPublicIp(ip: string): boolean { return !privateRanges.some(range => range.test(ip)); } +// 檢查是否為內部代理IP (如1Panel、Cloudflare等) +function isInternalProxyIp(ip: string): boolean { + // 1Panel 和常見代理服務器的內部IP範圍 + const proxyRanges = [ + /^172\.70\./, // Cloudflare 內部IP (包含你遇到的 172.70.214.246) + /^172\.71\./, // Cloudflare 內部IP + /^172\.64\./, // Cloudflare 內部IP + /^172\.65\./, // Cloudflare 內部IP + /^172\.66\./, // Cloudflare 內部IP + /^172\.67\./, // Cloudflare 內部IP + /^172\.68\./, // Cloudflare 內部IP + /^172\.69\./, // Cloudflare 內部IP + /^172\.72\./, // Cloudflare 內部IP + /^172\.73\./, // Cloudflare 內部IP + /^172\.74\./, // Cloudflare 內部IP + /^172\.75\./, // Cloudflare 內部IP + /^172\.76\./, // Cloudflare 內部IP + /^172\.77\./, // Cloudflare 內部IP + /^172\.78\./, // Cloudflare 內部IP + /^172\.79\./, // Cloudflare 內部IP + /^172\.80\./, // Cloudflare 內部IP + /^172\.81\./, // Cloudflare 內部IP + /^172\.82\./, // Cloudflare 內部IP + /^172\.83\./, // Cloudflare 內部IP + /^172\.84\./, // Cloudflare 內部IP + /^172\.85\./, // Cloudflare 內部IP + /^172\.86\./, // Cloudflare 內部IP + /^172\.87\./, // Cloudflare 內部IP + /^172\.88\./, // Cloudflare 內部IP + /^172\.89\./, // Cloudflare 內部IP + /^172\.90\./, // Cloudflare 內部IP + /^172\.91\./, // Cloudflare 內部IP + /^172\.92\./, // Cloudflare 內部IP + /^172\.93\./, // Cloudflare 內部IP + /^172\.94\./, // Cloudflare 內部IP + /^172\.95\./, // Cloudflare 內部IP + /^172\.96\./, // Cloudflare 內部IP + /^172\.97\./, // Cloudflare 內部IP + /^172\.98\./, // Cloudflare 內部IP + /^172\.99\./, // Cloudflare 內部IP + /^172\.100\./, // Cloudflare 內部IP + /^172\.101\./, // Cloudflare 內部IP + /^172\.102\./, // Cloudflare 內部IP + /^172\.103\./, // Cloudflare 內部IP + /^172\.104\./, // Cloudflare 內部IP + /^172\.105\./, // Cloudflare 內部IP + /^172\.106\./, // Cloudflare 內部IP + /^172\.107\./, // Cloudflare 內部IP + /^172\.108\./, // Cloudflare 內部IP + /^172\.109\./, // Cloudflare 內部IP + /^172\.110\./, // Cloudflare 內部IP + /^172\.111\./, // Cloudflare 內部IP + /^172\.112\./, // Cloudflare 內部IP + /^172\.113\./, // Cloudflare 內部IP + /^172\.114\./, // Cloudflare 內部IP + /^172\.115\./, // Cloudflare 內部IP + /^172\.116\./, // Cloudflare 內部IP + /^172\.117\./, // Cloudflare 內部IP + /^172\.118\./, // Cloudflare 內部IP + /^172\.119\./, // Cloudflare 內部IP + /^172\.120\./, // Cloudflare 內部IP + /^172\.121\./, // Cloudflare 內部IP + /^172\.122\./, // Cloudflare 內部IP + /^172\.123\./, // Cloudflare 內部IP + /^172\.124\./, // Cloudflare 內部IP + /^172\.125\./, // Cloudflare 內部IP + /^172\.126\./, // Cloudflare 內部IP + /^172\.127\./, // Cloudflare 內部IP + /^172\.128\./, // Cloudflare 內部IP + /^172\.129\./, // Cloudflare 內部IP + /^172\.130\./, // Cloudflare 內部IP + /^172\.131\./, // Cloudflare 內部IP + /^172\.132\./, // Cloudflare 內部IP + /^172\.133\./, // Cloudflare 內部IP + /^172\.134\./, // Cloudflare 內部IP + /^172\.135\./, // Cloudflare 內部IP + /^172\.136\./, // Cloudflare 內部IP + /^172\.137\./, // Cloudflare 內部IP + /^172\.138\./, // Cloudflare 內部IP + /^172\.139\./, // Cloudflare 內部IP + /^172\.140\./, // Cloudflare 內部IP + /^172\.141\./, // Cloudflare 內部IP + /^172\.142\./, // Cloudflare 內部IP + /^172\.143\./, // Cloudflare 內部IP + /^172\.144\./, // Cloudflare 內部IP + /^172\.145\./, // Cloudflare 內部IP + /^172\.146\./, // Cloudflare 內部IP + /^172\.147\./, // Cloudflare 內部IP + /^172\.148\./, // Cloudflare 內部IP + /^172\.149\./, // Cloudflare 內部IP + /^172\.150\./, // Cloudflare 內部IP + /^172\.151\./, // Cloudflare 內部IP + /^172\.152\./, // Cloudflare 內部IP + /^172\.153\./, // Cloudflare 內部IP + /^172\.154\./, // Cloudflare 內部IP + /^172\.155\./, // Cloudflare 內部IP + /^172\.156\./, // Cloudflare 內部IP + /^172\.157\./, // Cloudflare 內部IP + /^172\.158\./, // Cloudflare 內部IP + /^172\.159\./, // Cloudflare 內部IP + /^172\.160\./, // Cloudflare 內部IP + /^172\.161\./, // Cloudflare 內部IP + /^172\.162\./, // Cloudflare 內部IP + /^172\.163\./, // Cloudflare 內部IP + /^172\.164\./, // Cloudflare 內部IP + /^172\.165\./, // Cloudflare 內部IP + /^172\.166\./, // Cloudflare 內部IP + /^172\.167\./, // Cloudflare 內部IP + /^172\.168\./, // Cloudflare 內部IP + /^172\.169\./, // Cloudflare 內部IP + /^172\.170\./, // Cloudflare 內部IP + /^172\.171\./, // Cloudflare 內部IP + /^172\.172\./, // Cloudflare 內部IP + /^172\.173\./, // Cloudflare 內部IP + /^172\.174\./, // Cloudflare 內部IP + /^172\.175\./, // Cloudflare 內部IP + /^172\.176\./, // Cloudflare 內部IP + /^172\.177\./, // Cloudflare 內部IP + /^172\.178\./, // Cloudflare 內部IP + /^172\.179\./, // Cloudflare 內部IP + /^172\.180\./, // Cloudflare 內部IP + /^172\.181\./, // Cloudflare 內部IP + /^172\.182\./, // Cloudflare 內部IP + /^172\.183\./, // Cloudflare 內部IP + /^172\.184\./, // Cloudflare 內部IP + /^172\.185\./, // Cloudflare 內部IP + /^172\.186\./, // Cloudflare 內部IP + /^172\.187\./, // Cloudflare 內部IP + /^172\.188\./, // Cloudflare 內部IP + /^172\.189\./, // Cloudflare 內部IP + /^172\.190\./, // Cloudflare 內部IP + /^172\.191\./, // Cloudflare 內部IP + /^172\.192\./, // Cloudflare 內部IP + /^172\.193\./, // Cloudflare 內部IP + /^172\.194\./, // Cloudflare 內部IP + /^172\.195\./, // Cloudflare 內部IP + /^172\.196\./, // Cloudflare 內部IP + /^172\.197\./, // Cloudflare 內部IP + /^172\.198\./, // Cloudflare 內部IP + /^172\.199\./, // Cloudflare 內部IP + /^172\.200\./, // Cloudflare 內部IP + /^172\.201\./, // Cloudflare 內部IP + /^172\.202\./, // Cloudflare 內部IP + /^172\.203\./, // Cloudflare 內部IP + /^172\.204\./, // Cloudflare 內部IP + /^172\.205\./, // Cloudflare 內部IP + /^172\.206\./, // Cloudflare 內部IP + /^172\.207\./, // Cloudflare 內部IP + /^172\.208\./, // Cloudflare 內部IP + /^172\.209\./, // Cloudflare 內部IP + /^172\.210\./, // Cloudflare 內部IP + /^172\.211\./, // Cloudflare 內部IP + /^172\.212\./, // Cloudflare 內部IP + /^172\.213\./, // Cloudflare 內部IP + /^172\.214\./, // Cloudflare 內部IP + /^172\.215\./, // Cloudflare 內部IP + /^172\.216\./, // Cloudflare 內部IP + /^172\.217\./, // Cloudflare 內部IP + /^172\.218\./, // Cloudflare 內部IP + /^172\.219\./, // Cloudflare 內部IP + /^172\.220\./, // Cloudflare 內部IP + /^172\.221\./, // Cloudflare 內部IP + /^172\.222\./, // Cloudflare 內部IP + /^172\.223\./, // Cloudflare 內部IP + /^172\.224\./, // Cloudflare 內部IP + /^172\.225\./, // Cloudflare 內部IP + /^172\.226\./, // Cloudflare 內部IP + /^172\.227\./, // Cloudflare 內部IP + /^172\.228\./, // Cloudflare 內部IP + /^172\.229\./, // Cloudflare 內部IP + /^172\.230\./, // Cloudflare 內部IP + /^172\.231\./, // Cloudflare 內部IP + /^172\.232\./, // Cloudflare 內部IP + /^172\.233\./, // Cloudflare 內部IP + /^172\.234\./, // Cloudflare 內部IP + /^172\.235\./, // Cloudflare 內部IP + /^172\.236\./, // Cloudflare 內部IP + /^172\.237\./, // Cloudflare 內部IP + /^172\.238\./, // Cloudflare 內部IP + /^172\.239\./, // Cloudflare 內部IP + /^172\.240\./, // Cloudflare 內部IP + /^172\.241\./, // Cloudflare 內部IP + /^172\.242\./, // Cloudflare 內部IP + /^172\.243\./, // Cloudflare 內部IP + /^172\.244\./, // Cloudflare 內部IP + /^172\.245\./, // Cloudflare 內部IP + /^172\.246\./, // Cloudflare 內部IP + /^172\.247\./, // Cloudflare 內部IP + /^172\.248\./, // Cloudflare 內部IP + /^172\.249\./, // Cloudflare 內部IP + /^172\.250\./, // Cloudflare 內部IP + /^172\.251\./, // Cloudflare 內部IP + /^172\.252\./, // Cloudflare 內部IP + /^172\.253\./, // Cloudflare 內部IP + /^172\.254\./, // Cloudflare 內部IP + /^172\.255\./, // Cloudflare 內部IP + ]; + + return proxyRanges.some(range => range.test(ip)); +} + // 驗證IP地址格式 export function isValidIp(ip: string): boolean { const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; diff --git a/package.json b/package.json index f5a52ba..e0a55d2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "build": "next build", "dev": "next dev -p 12027", "lint": "next lint", - "start": "next start -p 12027" + "start": "next start -p 12027", + "postinstall": "prisma generate" }, "dependencies": { "@hookform/resolvers": "^3.9.1",