93 lines
2.6 KiB
TypeScript
93 lines
2.6 KiB
TypeScript
/**
|
||
* IP 白名單檢查工具
|
||
*/
|
||
|
||
// 檢查IP是否在指定範圍內
|
||
function isIpInRange(ip: string, range: string): boolean {
|
||
// 處理單一IP
|
||
if (!range.includes('/')) {
|
||
return ip === range;
|
||
}
|
||
|
||
// 處理CIDR範圍 (例如: 192.168.1.0/24)
|
||
const [rangeIp, prefixLength] = range.split('/');
|
||
const mask = parseInt(prefixLength);
|
||
|
||
if (isNaN(mask) || mask < 0 || mask > 32) {
|
||
return false;
|
||
}
|
||
|
||
const ipNum = ipToNumber(ip);
|
||
const rangeNum = ipToNumber(rangeIp);
|
||
const maskNum = (0xFFFFFFFF << (32 - mask)) >>> 0;
|
||
|
||
return (ipNum & maskNum) === (rangeNum & maskNum);
|
||
}
|
||
|
||
// 將IP地址轉換為數字
|
||
function ipToNumber(ip: string): number {
|
||
const parts = ip.split('.').map(part => parseInt(part));
|
||
if (parts.length !== 4 || parts.some(part => isNaN(part) || part < 0 || part > 255)) {
|
||
throw new Error(`Invalid IP address: ${ip}`);
|
||
}
|
||
return (parts[0] << 24) + (parts[1] << 16) + (parts[2] << 8) + parts[3];
|
||
}
|
||
|
||
// 檢查IP是否在白名單內
|
||
export function isIpAllowed(clientIp: string, allowedIps: string): boolean {
|
||
if (!allowedIps || allowedIps.trim() === '') {
|
||
return true; // 如果沒有設置白名單,允許所有IP
|
||
}
|
||
|
||
const allowedIpList = allowedIps.split(',').map(ip => ip.trim()).filter(ip => ip);
|
||
|
||
for (const allowedIp of allowedIpList) {
|
||
try {
|
||
if (isIpInRange(clientIp, allowedIp)) {
|
||
return true;
|
||
}
|
||
} catch (error) {
|
||
console.error(`Invalid IP range: ${allowedIp}`, error);
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// 獲取客戶端真實IP
|
||
export function getClientIp(req: any): string {
|
||
// 檢查各種可能的IP來源
|
||
const ipSources = [
|
||
req.headers['x-forwarded-for'],
|
||
req.headers['x-real-ip'],
|
||
req.headers['x-client-ip'],
|
||
req.headers['cf-connecting-ip'], // Cloudflare
|
||
req.connection?.remoteAddress,
|
||
req.socket?.remoteAddress,
|
||
req.ip
|
||
];
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
return '127.0.0.1'; // 默認本地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]?)$/;
|
||
return ipRegex.test(ip);
|
||
}
|
||
|
||
// 驗證CIDR格式
|
||
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);
|
||
}
|