From d4b9b2eb50216a83d70655d30b177439237d70c1 Mon Sep 17 00:00:00 2001 From: Bojan Kucera Date: Thu, 5 Jun 2025 22:27:33 -0400 Subject: [PATCH] updated code --- src/index.ts | 87 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index c1ce5c8..fd06248 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,16 +21,64 @@ const NODE_ENV = process.env.NODE_ENV || 'production'; const objs: ApiResponse[] = [] -// Create Fastify instance with trust proxy enabled +// Create Fastify instance with comprehensive trust proxy const fastify = Fastify({ logger: NODE_ENV === 'development', - trustProxy: process.env.TRUST_PROXY === 'true' || true + trustProxy: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '127.0.0.1', '::1'] // Trust k8s networks }); +// Enhanced IP detection function +function getClientIP(request: FastifyRequest): string { + const headers = request.headers; + + // Try multiple headers in order of preference + const ipSources = [ + headers['cf-connecting-ip']?.toString(), // Cloudflare + headers['true-client-ip']?.toString(), // Akamai/other CDNs + headers['x-forwarded-for']?.toString().split(',')[0]?.trim(), // Most common + headers['x-client-ip']?.toString(), // Apache + headers['x-cluster-client-ip']?.toString(), // Cluster + headers['forwarded']?.toString().match(/for=([^;,\s]+)/)?.[1], // RFC 7239 + request.ip, // Fastify default + request.socket.remoteAddress // Socket + ]; + + // Filter out internal/private IPs and return first public IP + for (const ip of ipSources) { + if (ip && ip !== 'unknown' && !isPrivateIP(ip)) { + return ip; + } + } + + // If no public IP found, return the first non-unknown IP + return ipSources.find(ip => ip && ip !== 'unknown') || 'unknown'; +} + +// Check if IP is private/internal +function isPrivateIP(ip: string): boolean { + if (!ip || ip === 'unknown') return true; + + // Remove any port number + const cleanIP = ip.split(':')[0]; + + // Private IP ranges + 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 (localhost) + /^169\.254\./, // 169.254.0.0/16 (link-local) + /^::1$/, // IPv6 localhost + /^fe80:/ // IPv6 link-local + ]; + + return privateRanges.some(range => range.test(cleanIP)); +} + // API Key authentication middleware fastify.addHook('preHandler', async (request: FastifyRequest, reply: FastifyReply) => { - // Skip auth for health check endpoint - if (request.url === '/health') { + // Skip auth for health check and debug endpoints + if (request.url === '/health' || request.url === '/ip-debug') { return; } @@ -55,9 +103,8 @@ fastify.addHook('preHandler', async (request: FastifyRequest, reply: FastifyRepl // Helper function to get full IP chain function getFullIPChain(request: FastifyRequest): { clientIP: string, ipChain: string[] } { - // Fastify automatically parses X-Forwarded-For when trustProxy is enabled - const clientIP = request.ip || 'unknown'; - const ipChain = request.ips || []; // Full chain of IPs + const clientIP = getClientIP(request); + const ipChain = request.ips || []; return { clientIP, @@ -84,6 +131,27 @@ const start = async () => { }; }); + // IP Debug endpoint (bypasses authentication) - for troubleshooting + fastify.get('/ip-debug', async (request) => { + return { + detectedClientIP: getClientIP(request), + fastifyIP: request.ip, + fastifyIPs: request.ips, + socketIP: request.socket.remoteAddress, + allIPHeaders: { + 'x-forwarded-for': request.headers['x-forwarded-for'], + 'x-real-ip': request.headers['x-real-ip'], + 'cf-connecting-ip': request.headers['cf-connecting-ip'], + 'true-client-ip': request.headers['true-client-ip'], + 'x-client-ip': request.headers['x-client-ip'], + 'x-cluster-client-ip': request.headers['x-cluster-client-ip'], + 'forwarded': request.headers['forwarded'] + }, + isPrivateIP: isPrivateIP(request.headers['x-real-ip']?.toString() || ''), + timestamp: Date.now() + }; + }); + // Random endpoint for testing fastify.get('/random', async () => { return objs[Math.round(objs.length * Math.random())] || { message: 'No data yet' }; @@ -103,7 +171,7 @@ const start = async () => { const ipInfo = getFullIPChain(request); const obj = { success: true, - clientIP: ipInfo.clientIP, // Fastify's auto-detected client IP + clientIP: ipInfo.clientIP, // Enhanced client IP detection ipChain: ipInfo.ipChain, // Full proxy chain foundIPs: result.ips, // IPs found by our custom parser totalFound: result.ips.length, @@ -122,6 +190,7 @@ const start = async () => { console.log(`🚀 Proxy Detection API running on http://localhost:${PORT}`); console.log(`📍 Available endpoints:`); console.log(` GET /health - Health check (no auth required)`); + console.log(` GET /ip-debug - IP debugging info (no auth required)`); console.log(` GET / - Extract all IP addresses from request headers`); console.log(` GET /random - Get random cached result`); console.log(`🔑 API Key required for protected endpoints`); @@ -131,4 +200,4 @@ const start = async () => { } }; -start(); +start(); \ No newline at end of file