做 ip 防呆機制

This commit is contained in:
2025-08-01 13:17:32 +08:00
parent 2282eed9a1
commit a54ae31896
7 changed files with 1139 additions and 125 deletions

View File

@@ -54,30 +54,132 @@ export function isIpAllowed(clientIp: string, allowedIps: string): boolean {
return false;
}
// 清理和驗證IP地址
function cleanIpAddress(ip: string): string | null {
if (!ip) return null;
// 移除空白字符
ip = ip.trim();
// 處理IPv6格式的IPv4地址 (例如: ::ffff:192.168.1.1)
if (ip.startsWith('::ffff:')) {
ip = ip.substring(7);
}
// 驗證IP格式
if (!isValidIp(ip)) {
return null;
}
return ip;
}
// 獲取客戶端真實IP
export function getClientIp(req: any): string {
// 檢查各種可能的IP來源
// 按優先順序檢查各種IP來源
const ipSources = [
// 代理伺服器轉發的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-forwarded'], // 舊版代理頭
req.headers['forwarded-for'],
req.headers['forwarded'],
// 直接連接的IP
req.connection?.remoteAddress,
req.socket?.remoteAddress,
req.ip
req.ip,
// Next.js 特定的IP來源
req.headers['x-original-forwarded-for'],
req.headers['x-cluster-client-ip'],
];
// 收集所有找到的IP用於調試
const foundIps: string[] = [];
for (const ipSource of ipSources) {
if (ipSource) {
// 處理多個IP的情況 (例如: "192.168.1.1, 10.0.0.1")
const firstIp = ipSource.toString().split(',')[0].trim();
if (firstIp && firstIp !== '::1' && firstIp !== '127.0.0.1') {
return firstIp;
// 處理多個IP的情況 (例如: "192.168.1.1, 10.0.0.1, 203.0.113.1")
const ipList = ipSource.toString().split(',').map(ip => ip.trim());
// 遍歷所有IP找到第一個有效的公網IP
for (const ip of ipList) {
const cleanIp = cleanIpAddress(ip);
if (cleanIp) {
foundIps.push(cleanIp);
// 檢查是否為公網IP
if (isPublicIp(cleanIp)) {
return cleanIp;
}
}
}
}
}
return '127.0.0.1'; // 默認本地IP
// 如果沒有找到有效的公網IP返回第一個有效的IP
for (const ipSource of ipSources) {
if (ipSource) {
const ipList = ipSource.toString().split(',').map(ip => ip.trim());
for (const ip of ipList) {
const cleanIp = cleanIpAddress(ip);
if (cleanIp) {
return cleanIp;
}
}
}
}
// 在本地開發環境中嘗試獲取本機IP
if (process.env.NODE_ENV === 'development') {
const localIp = getLocalIp();
if (localIp && localIp !== '127.0.0.1') {
return localIp;
}
}
// 最後的備用方案
return '127.0.0.1';
}
// 獲取本機IP地址僅用於開發環境
function getLocalIp(): string | null {
try {
const os = require('os');
const interfaces = os.networkInterfaces();
for (const name of Object.keys(interfaces)) {
for (const networkInterface of interfaces[name]) {
// 跳過內部非IPv4和回環地址
if (networkInterface.family === 'IPv4' && !networkInterface.internal) {
return networkInterface.address;
}
}
}
} catch (error) {
console.error('Error getting local IP:', error);
}
return null;
}
// 檢查是否為公網IP
function isPublicIp(ip: string): boolean {
// 私有IP範圍
const privateRanges = [
/^10\./, // 10.0.0.0/8
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12
/^192\.168\./, // 192.168.0.0/16
/^127\./, // 127.0.0.0/8
/^169\.254\./, // 169.254.0.0/16 (Link-local)
/^0\./, // 0.0.0.0/8
/^224\./, // 224.0.0.0/4 (Multicast)
/^240\./, // 240.0.0.0/4 (Reserved)
];
return !privateRanges.some(range => range.test(ip));
}
// 驗證IP地址格式
@@ -90,4 +192,86 @@ export function isValidIp(ip: string): boolean {
export function isValidCidr(cidr: string): boolean {
const cidrRegex = /^(?:(?: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]?)\/([0-9]|[1-2][0-9]|3[0-2])$/;
return cidrRegex.test(cidr);
}
// 獲取IP地理位置信息可選功能
export async function getIpLocation(ip: string): Promise<any> {
try {
const response = await fetch(`http://ip-api.com/json/${ip}?fields=status,message,country,countryCode,region,regionName,city,zip,lat,lon,timezone,isp,org,as,mobile,proxy,hosting,query`);
if (response.ok) {
return await response.json();
}
} catch (error) {
console.error('Error fetching IP location:', error);
}
return null;
}
// 獲取詳細的IP檢測信息用於調試
export function getDetailedIpInfo(req: any): {
detectedIp: string;
allFoundIps: string[];
ipSources: Record<string, string | null>;
isLocalDevelopment: boolean;
localIp: string | null;
} {
const ipSources = {
'x-forwarded-for': req.headers['x-forwarded-for'],
'x-real-ip': req.headers['x-real-ip'],
'x-client-ip': req.headers['x-client-ip'],
'cf-connecting-ip': req.headers['cf-connecting-ip'],
'x-forwarded': req.headers['x-forwarded'],
'forwarded-for': req.headers['forwarded-for'],
'forwarded': req.headers['forwarded'],
'x-original-forwarded-for': req.headers['x-original-forwarded-for'],
'x-cluster-client-ip': req.headers['x-cluster-client-ip'],
'connection.remoteAddress': req.connection?.remoteAddress,
'socket.remoteAddress': req.socket?.remoteAddress,
'req.ip': req.ip,
};
const allFoundIps: string[] = [];
let detectedIp = '127.0.0.1';
// 收集所有IP
Object.values(ipSources).forEach(ipSource => {
if (ipSource) {
const ipList = ipSource.toString().split(',').map(ip => ip.trim());
ipList.forEach(ip => {
const cleanIp = cleanIpAddress(ip);
if (cleanIp && !allFoundIps.includes(cleanIp)) {
allFoundIps.push(cleanIp);
}
});
}
});
// 選擇最佳IP
for (const ip of allFoundIps) {
if (isPublicIp(ip)) {
detectedIp = ip;
break;
}
}
// 如果沒有公網IP選擇第一個非回環IP
if (detectedIp === '127.0.0.1') {
for (const ip of allFoundIps) {
if (ip !== '127.0.0.1' && ip !== '::1') {
detectedIp = ip;
break;
}
}
}
const isLocalDevelopment = process.env.NODE_ENV === 'development';
const localIp = isLocalDevelopment ? getLocalIp() : null;
return {
detectedIp,
allFoundIps,
ipSources,
isLocalDevelopment,
localIp
};
}