Files
ai-showcase-platform/lib/smart-ip-detector.ts
2025-09-21 03:08:41 +08:00

273 lines
8.3 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.

// =====================================================
// 智能 IP 偵測器
// =====================================================
import { NextRequest } from 'next/server';
export interface IPDetectionResult {
detectedIP: string;
confidence: 'high' | 'medium' | 'low';
source: string;
allCandidates: string[];
isPublicIP: boolean;
}
export class SmartIPDetector {
private static instance: SmartIPDetector;
public static getInstance(): SmartIPDetector {
if (!SmartIPDetector.instance) {
SmartIPDetector.instance = new SmartIPDetector();
}
return SmartIPDetector.instance;
}
// 檢查是否為公網 IP
private isPublicIP(ip: string): boolean {
if (!ip || ip === 'unknown') return false;
// 本地地址
if (ip === '127.0.0.1' || ip === '::1' || ip === 'localhost') return false;
// 私有地址範圍
if (ip.startsWith('192.168.') ||
ip.startsWith('10.') ||
ip.startsWith('172.') ||
ip.startsWith('169.254.')) return false;
// IPv6 本地地址
if (ip.startsWith('fe80:') ||
ip.startsWith('::1') ||
ip.startsWith('::ffff:127.0.0.1')) return false;
return true;
}
// 檢查是否為基礎設施 IPVercel、AWS、Cloudflare 等)
private isInfrastructureIP(ip: string): boolean {
if (!ip) return false;
// Vercel/AWS EC2 實例
if (ip.includes('ec2-') && ip.includes('.compute-1.amazonaws.com')) return true;
if (ip.includes('ec2-') && ip.includes('.amazonaws.com')) return true;
// AWS 其他服務
if (ip.includes('.amazonaws.com')) return true;
// Vercel 相關
if (ip.includes('vercel')) return true;
// Cloudflare 相關(但 cf-connecting-ip 是我們想要的)
if (ip.includes('cloudflare') && !ip.includes('cf-connecting-ip')) return true;
// 其他常見的 CDN/代理服務
const infrastructurePatterns = [
'fastly.com',
'cloudfront.net',
'akamai.net',
'maxcdn.com',
'keycdn.com'
];
return infrastructurePatterns.some(pattern => ip.includes(pattern));
}
// 檢查是否為用戶真實 IP
private isUserRealIP(ip: string): boolean {
if (!ip || ip === 'unknown') return false;
// 不是基礎設施 IP
if (this.isInfrastructureIP(ip)) return false;
// 是公網 IP
if (!this.isPublicIP(ip)) return false;
// 是有效的 IP 格式
if (!this.isValidIP(ip)) return false;
return true;
}
// 從 x-forwarded-for 解析 IP
private parseForwardedFor(forwarded: string): string[] {
if (!forwarded) return [];
return forwarded
.split(',')
.map(ip => ip.trim())
.filter(ip => ip && ip !== 'unknown');
}
// 智能偵測客戶端 IP
public detectClientIP(request: NextRequest): IPDetectionResult {
const candidates: string[] = [];
const sources: { [key: string]: string } = {};
// 1. 收集所有可能的 IP 來源
const headers = {
'x-forwarded-for': request.headers.get('x-forwarded-for'),
'x-real-ip': request.headers.get('x-real-ip'),
'cf-connecting-ip': request.headers.get('cf-connecting-ip'),
'x-client-ip': request.headers.get('x-client-ip'),
'x-forwarded': request.headers.get('x-forwarded'),
'x-cluster-client-ip': request.headers.get('x-cluster-client-ip'),
'x-original-forwarded-for': request.headers.get('x-original-forwarded-for'),
'x-remote-addr': request.headers.get('x-remote-addr'),
'remote-addr': request.headers.get('remote-addr'),
'client-ip': request.headers.get('client-ip'),
};
// 2. 從各個標頭提取 IP
Object.entries(headers).forEach(([header, value]) => {
if (value) {
if (header === 'x-forwarded-for') {
const ips = this.parseForwardedFor(value);
ips.forEach(ip => {
candidates.push(ip);
sources[ip] = header;
});
} else {
candidates.push(value);
sources[value] = header;
}
}
});
// 3. 添加 NextRequest 的 IP (如果可用)
const nextIP = (request as any).ip;
if (nextIP) {
candidates.push(nextIP);
sources[nextIP] = 'next-request';
}
// 4. 去重並過濾
const uniqueCandidates = [...new Set(candidates)].filter(ip =>
ip && ip !== 'unknown' && ip !== '::1' && ip !== '127.0.0.1'
);
console.log('🔍 IP 偵測候選:', {
candidates: uniqueCandidates,
sources: sources,
allHeaders: Object.fromEntries(request.headers.entries())
});
// 5. 智能選擇最佳 IP
let selectedIP = 'unknown';
let confidence: 'high' | 'medium' | 'low' = 'low';
let source = 'unknown';
// 優先級 1: Cloudflare IP (最高可信度) - 但要是用戶真實 IP
const cfIP = uniqueCandidates.find(ip => sources[ip] === 'cf-connecting-ip');
if (cfIP && this.isUserRealIP(cfIP)) {
selectedIP = cfIP;
confidence = 'high';
source = 'cf-connecting-ip';
}
// 優先級 2: 其他代理標頭中的用戶真實 IP
if (selectedIP === 'unknown') {
const proxyHeaders = ['x-real-ip', 'x-client-ip', 'x-cluster-client-ip'];
for (const header of proxyHeaders) {
const ip = uniqueCandidates.find(candidate => sources[candidate] === header);
if (ip && this.isUserRealIP(ip)) {
selectedIP = ip;
confidence = 'high';
source = header;
break;
}
}
}
// 優先級 3: x-forwarded-for 中的第一個用戶真實 IP
if (selectedIP === 'unknown') {
const forwardedIPs = uniqueCandidates.filter(ip => sources[ip] === 'x-forwarded-for');
const userRealIP = forwardedIPs.find(ip => this.isUserRealIP(ip));
if (userRealIP) {
selectedIP = userRealIP;
confidence = 'medium';
source = 'x-forwarded-for';
}
}
// 優先級 4: 任何用戶真實 IP
if (selectedIP === 'unknown') {
const userRealIP = uniqueCandidates.find(ip => this.isUserRealIP(ip));
if (userRealIP) {
selectedIP = userRealIP;
confidence = 'medium';
source = sources[userRealIP] || 'unknown';
}
}
// 優先級 5: 任何公網 IP非基礎設施
if (selectedIP === 'unknown') {
const publicIP = uniqueCandidates.find(ip =>
this.isPublicIP(ip) && !this.isInfrastructureIP(ip)
);
if (publicIP) {
selectedIP = publicIP;
confidence = 'low';
source = sources[publicIP] || 'unknown';
}
}
// 優先級 6: 任何非本地 IP非基礎設施
if (selectedIP === 'unknown') {
const nonLocalIP = uniqueCandidates.find(ip =>
ip !== '::1' && ip !== '127.0.0.1' &&
!ip.startsWith('192.168.') && !ip.startsWith('10.') && !ip.startsWith('172.') &&
!this.isInfrastructureIP(ip)
);
if (nonLocalIP) {
selectedIP = nonLocalIP;
confidence = 'low';
source = sources[nonLocalIP] || 'unknown';
}
}
// 優先級 7: 第一個候選 IP最後選擇
if (selectedIP === 'unknown' && uniqueCandidates.length > 0) {
selectedIP = uniqueCandidates[0];
confidence = 'low';
source = sources[selectedIP] || 'unknown';
}
const result: IPDetectionResult = {
detectedIP: selectedIP,
confidence,
source,
allCandidates: uniqueCandidates,
isPublicIP: this.isPublicIP(selectedIP)
};
console.log('🎯 IP 偵測結果:', {
...result,
isInfrastructureIP: this.isInfrastructureIP(selectedIP),
isUserRealIP: this.isUserRealIP(selectedIP),
infrastructureIPs: uniqueCandidates.filter(ip => this.isInfrastructureIP(ip)),
userRealIPs: uniqueCandidates.filter(ip => this.isUserRealIP(ip))
});
return result;
}
// 驗證 IP 格式
public isValidIP(ip: string): boolean {
if (!ip || ip === 'unknown') return false;
// IPv4 格式檢查
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipv4Regex.test(ip)) {
const parts = ip.split('.').map(Number);
return parts.every(part => part >= 0 && part <= 255);
}
// IPv6 格式檢查(簡化)
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
return ipv6Regex.test(ip);
}
}
// 導出單例實例
export const smartIPDetector = SmartIPDetector.getInstance();