Spaces:
Build error
Build error
| import axios from 'axios'; | |
| import { config } from '@config/index.js'; | |
| interface VPNAPIResponse { | |
| security: { | |
| is_vpn: boolean; | |
| is_proxy: boolean; | |
| is_tor: boolean; | |
| is_relay: boolean; | |
| is_datacenter: boolean; | |
| }; | |
| network: { | |
| asn: number; | |
| org: string; | |
| }; | |
| } | |
| const VPN_CHECK_CACHE = new Map<string, { result: boolean; timestamp: number }>(); | |
| const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours | |
| export async function checkVPN(ip: string): Promise<{ | |
| isVPN: boolean; | |
| isProxy: boolean; | |
| isDatacenter: boolean; | |
| isTOR: boolean; | |
| details?: string; | |
| }> { | |
| if (!config.VPN_DETECTION_ENABLED) { | |
| return { isVPN: false, isProxy: false, isDatacenter: false, isTOR: false }; | |
| } | |
| // Skip local/private IPs | |
| if (isPrivateIP(ip)) { | |
| return { isVPN: false, isProxy: false, isDatacenter: false, isTOR: false }; | |
| } | |
| // Check cache | |
| const cached = VPN_CHECK_CACHE.get(ip); | |
| if (cached && Date.now() - cached.timestamp < CACHE_TTL) { | |
| return { isVPN: cached.result, isProxy: false, isDatacenter: false, isTOR: false }; | |
| } | |
| try { | |
| let result: { isVPN: boolean; isProxy: boolean; isDatacenter: boolean; isTOR: boolean; details?: string }; | |
| if (config.IPAPI_KEY) { | |
| result = await checkWithIPAPI(ip); | |
| } else { | |
| result = await checkWithFreeAPI(ip); | |
| } | |
| VPN_CHECK_CACHE.set(ip, { result: result.isVPN || result.isProxy || result.isDatacenter, timestamp: Date.now() }); | |
| return result; | |
| } catch (error) { | |
| // On error, allow the request (fail open) | |
| console.warn(`VPN check failed for ${ip}:`, error); | |
| return { isVPN: false, isProxy: false, isDatacenter: false, isTOR: false, details: 'Check failed' }; | |
| } | |
| } | |
| async function checkWithIPAPI(ip: string) { | |
| const response = await axios.get(`http://ip-api.com/json/${ip}?fields=status,proxy,hosting,as,org`, { | |
| timeout: 5000, | |
| headers: { 'User-Agent': 'WhatsApp-Channel-Directory/1.0' }, | |
| }); | |
| if (response.data.status !== 'success') { | |
| throw new Error('IP API request failed'); | |
| } | |
| return { | |
| isVPN: false, | |
| isProxy: response.data.proxy === true, | |
| isDatacenter: response.data.hosting === true, | |
| isTOR: false, | |
| details: `ASN: ${response.data.as}, Org: ${response.data.org}`, | |
| }; | |
| } | |
| async function checkWithFreeAPI(ip: string): Promise<{ | |
| isVPN: boolean; | |
| isProxy: boolean; | |
| isDatacenter: boolean; | |
| isTOR: boolean; | |
| details?: string; | |
| }> { | |
| // Use a free API or local list - for now, return safe defaults | |
| // In production, integrate with a proper VPN detection service | |
| return { | |
| isVPN: false, | |
| isProxy: false, | |
| isDatacenter: false, | |
| isTOR: false, | |
| details: 'Free tier - limited detection', | |
| }; | |
| } | |
| function isPrivateIP(ip: string): boolean { | |
| const privateRanges = [ | |
| /^10\./, | |
| /^192\.168\./, | |
| /^172\.(1[6-9]|2[0-9]|3[0-1])\./, | |
| /^127\./, | |
| /^::1$/, | |
| /^fe80::/, | |
| /^fc00:/, | |
| /^fd00:/, | |
| ]; | |
| return privateRanges.some(range => range.test(ip)); | |
| } | |
| export function getClientIP(request: { headers: Record<string, string | string[] | undefined>; ip?: string }): string { | |
| // Check various headers for real IP (behind proxy) | |
| const forwarded = request.headers['x-forwarded-for']; | |
| if (forwarded) { | |
| const ips = (Array.isArray(forwarded) ? forwarded[0] : forwarded).split(',').map(ip => ip.trim()); | |
| return ips[0]; | |
| } | |
| const realIP = request.headers['x-real-ip']; | |
| if (realIP) { | |
| return Array.isArray(realIP) ? realIP[0] : realIP; | |
| } | |
| return request.ip || 'unknown'; | |
| } |