reogranized app and added more headers
This commit is contained in:
parent
1048ab5c20
commit
223f426a58
10 changed files with 925 additions and 218 deletions
35
src/config.ts
Normal file
35
src/config.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Configuration and environment variables
|
||||
*/
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
key: process.env.API_KEY,
|
||||
port: parseInt(process.env.PORT || '2424'),
|
||||
host: process.env.HOST || '0.0.0.0',
|
||||
version: '1.0.0'
|
||||
},
|
||||
|
||||
environment: {
|
||||
nodeEnv: process.env.NODE_ENV || 'production',
|
||||
isDevelopment: process.env.NODE_ENV === 'development'
|
||||
},
|
||||
|
||||
server: {
|
||||
// Trust proxy settings for Kubernetes and common networks
|
||||
trustProxy: [
|
||||
'10.0.0.0/8', // Private networks
|
||||
'172.16.0.0/12', // Docker/K8s networks
|
||||
'192.168.0.0/16', // Local networks
|
||||
'127.0.0.1', // Localhost
|
||||
'::1' // IPv6 localhost
|
||||
],
|
||||
|
||||
cors: {
|
||||
origin: true,
|
||||
methods: ['GET', 'POST', 'OPTIONS']
|
||||
},
|
||||
|
||||
maxCachedResults: 1000
|
||||
}
|
||||
};
|
||||
156
src/handlers.ts
Normal file
156
src/handlers.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* Route handlers for the proxy detection API
|
||||
*/
|
||||
|
||||
import { FastifyRequest } from 'fastify';
|
||||
import { extractIPsFromHeaders } from './proxyService';
|
||||
import { ApiResponse, HealthResponse, DetailedDebugResponse } from './types';
|
||||
import { config } from './config';
|
||||
import {
|
||||
getClientIP,
|
||||
getFullIPChain,
|
||||
normalizeHeaders,
|
||||
extractGeolocation,
|
||||
extractProxyInfo
|
||||
} from './utils';
|
||||
|
||||
// In-memory cache for results
|
||||
const cachedResults: ApiResponse[] = [];
|
||||
|
||||
/**
|
||||
* Health check endpoint
|
||||
*/
|
||||
export async function healthHandler(): Promise<HealthResponse> {
|
||||
return {
|
||||
status: 'healthy',
|
||||
timestamp: Date.now(),
|
||||
uptime: process.uptime(),
|
||||
version: config.api.version
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Main proxy detection endpoint
|
||||
*/
|
||||
export async function mainHandler(request: FastifyRequest): Promise<ApiResponse> {
|
||||
const headers = request.headers;
|
||||
const normalizedHeaders = normalizeHeaders(headers);
|
||||
|
||||
const result = extractIPsFromHeaders(normalizedHeaders);
|
||||
const ipInfo = getFullIPChain(request);
|
||||
|
||||
// Enhanced response with more context
|
||||
const response: ApiResponse = {
|
||||
success: true,
|
||||
clientIP: ipInfo.clientIP,
|
||||
ipChain: ipInfo.ipChain,
|
||||
foundIPs: result.ips,
|
||||
totalFound: result.ips.length,
|
||||
headersSources: result.sources,
|
||||
|
||||
// Additional context
|
||||
geolocation: extractGeolocation(normalizedHeaders),
|
||||
proxy: extractProxyInfo(normalizedHeaders),
|
||||
|
||||
allHeaders: normalizedHeaders,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// Cache management
|
||||
if (cachedResults.length > config.server.maxCachedResults) {
|
||||
cachedResults.shift();
|
||||
}
|
||||
cachedResults.push(response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Random cached result endpoint
|
||||
*/
|
||||
export async function randomHandler(): Promise<ApiResponse | { message: string }> {
|
||||
if (cachedResults.length === 0) {
|
||||
return { message: 'No data yet' };
|
||||
}
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * cachedResults.length);
|
||||
return cachedResults[randomIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed debug endpoint with all possible headers
|
||||
*/
|
||||
export async function detailedDebugHandler(request: FastifyRequest): Promise<DetailedDebugResponse> {
|
||||
const headers = request.headers;
|
||||
|
||||
return {
|
||||
allSources: {
|
||||
// CDN Headers
|
||||
'cf-connecting-ip': headers['cf-connecting-ip'],
|
||||
'cf-pseudo-ipv4': headers['cf-pseudo-ipv4'],
|
||||
'cf-ipcountry': headers['cf-ipcountry'],
|
||||
'true-client-ip': headers['true-client-ip'],
|
||||
'x-akamai-edgescape': headers['x-akamai-edgescape'],
|
||||
'fastly-client-ip': headers['fastly-client-ip'],
|
||||
|
||||
// Standard Headers
|
||||
'x-forwarded-for': headers['x-forwarded-for'],
|
||||
'x-original-forwarded-for': headers['x-original-forwarded-for'],
|
||||
'x-client-ip': headers['x-client-ip'],
|
||||
'x-real-ip': headers['x-real-ip'],
|
||||
'x-originating-ip': headers['x-originating-ip'],
|
||||
'x-remote-ip': headers['x-remote-ip'],
|
||||
'x-remote-addr': headers['x-remote-addr'],
|
||||
|
||||
// Load Balancer
|
||||
'x-cluster-client-ip': headers['x-cluster-client-ip'],
|
||||
'forwarded': headers['forwarded'],
|
||||
'x-forwarded': headers['x-forwarded'],
|
||||
'x-appengine-remote-addr': headers['x-appengine-remote-addr'],
|
||||
|
||||
// Cloud Provider
|
||||
'x-azure-clientip': headers['x-azure-clientip'],
|
||||
'x-azure-ref': headers['x-azure-ref'],
|
||||
|
||||
// Proxy/Firewall
|
||||
'wl-proxy-client-ip': headers['wl-proxy-client-ip'],
|
||||
'proxy-client-ip': headers['proxy-client-ip'],
|
||||
'x-sucuri-clientip': headers['x-sucuri-clientip'],
|
||||
'incap-client-ip': headers['incap-client-ip'],
|
||||
|
||||
// Mobile/Carrier
|
||||
'x-nokia-msisdn': headers['x-nokia-msisdn'],
|
||||
'x-up-calling-line-id': headers['x-up-calling-line-id'],
|
||||
'http_client_ip': headers['http_client_ip'],
|
||||
'http_x_forwarded_for': headers['http_x_forwarded_for'],
|
||||
'remote_addr': headers['remote_addr'],
|
||||
|
||||
// Alternative
|
||||
'x-coming-from': headers['x-coming-from'],
|
||||
'x-forwarded-host': headers['x-forwarded-host'],
|
||||
'x-host': headers['x-host'],
|
||||
|
||||
// Framework defaults
|
||||
'fastify-ip': request.ip,
|
||||
'socket-remote': request.socket.remoteAddress
|
||||
},
|
||||
detectedClientIP: getClientIP(request),
|
||||
fastifyIPs: request.ips || [],
|
||||
|
||||
// Additional useful info
|
||||
geolocation: {
|
||||
'cf-ipcountry': headers['cf-ipcountry'], // Cloudflare country
|
||||
'cf-ray': headers['cf-ray'], // Cloudflare ray ID
|
||||
'x-forwarded-proto': headers['x-forwarded-proto'], // Protocol
|
||||
'x-forwarded-port': headers['x-forwarded-port'], // Port
|
||||
},
|
||||
|
||||
security: {
|
||||
'x-sucuri-country': headers['x-sucuri-country'],
|
||||
'x-forwarded-ssl': headers['x-forwarded-ssl'],
|
||||
'x-url-scheme': headers['x-url-scheme'],
|
||||
},
|
||||
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
236
src/index.ts
236
src/index.ts
|
|
@ -1,221 +1,21 @@
|
|||
import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { extractIPsFromHeaders } from './proxyService';
|
||||
/**
|
||||
* Proxy Detection API
|
||||
* Entry point for the application
|
||||
*/
|
||||
|
||||
// Type definitions
|
||||
interface ApiResponse {
|
||||
success: boolean;
|
||||
clientIP: string;
|
||||
ipChain: string[];
|
||||
foundIPs: string[];
|
||||
totalFound: number;
|
||||
headersSources: Record<string, string[]>;
|
||||
allHeaders: Record<string, string>;
|
||||
timestamp: number;
|
||||
import { createServer, configureServer, startServer } from './server';
|
||||
|
||||
/**
|
||||
* Initialize and start the proxy detection API
|
||||
*/
|
||||
async function main() {
|
||||
const server = createServer();
|
||||
await configureServer(server);
|
||||
await startServer(server);
|
||||
}
|
||||
|
||||
// Environment configuration
|
||||
const API_KEY = process.env.API_KEY;
|
||||
const PORT = parseInt(process.env.PORT || '2424');
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
const NODE_ENV = process.env.NODE_ENV || 'production';
|
||||
|
||||
const objs: ApiResponse[] = []
|
||||
|
||||
// Create Fastify instance with comprehensive trust proxy
|
||||
const fastify = Fastify({
|
||||
logger: NODE_ENV === 'development',
|
||||
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-original-forwarded-for']?.toString().split(',')[0]?.trim(), // Original
|
||||
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
|
||||
];
|
||||
|
||||
// Don't filter private IPs for now - let's see what we get
|
||||
return ipSources.find(ip => ip && ip !== 'unknown') || 'unknown';
|
||||
}
|
||||
|
||||
// Add a more detailed debug endpoint
|
||||
fastify.get('/ip-debug-detailed', async (request) => {
|
||||
const headers = request.headers;
|
||||
|
||||
return {
|
||||
allSources: {
|
||||
'cf-connecting-ip': headers['cf-connecting-ip'],
|
||||
'true-client-ip': headers['true-client-ip'],
|
||||
'x-forwarded-for': headers['x-forwarded-for'],
|
||||
'x-original-forwarded-for': headers['x-original-forwarded-for'],
|
||||
'x-client-ip': headers['x-client-ip'],
|
||||
'x-real-ip': headers['x-real-ip'],
|
||||
'x-cluster-client-ip': headers['x-cluster-client-ip'],
|
||||
'forwarded': headers['forwarded'],
|
||||
'fastify-ip': request.ip,
|
||||
'socket-remote': request.socket.remoteAddress
|
||||
},
|
||||
detectedClientIP: getClientIP(request),
|
||||
fastifyIPs: request.ips,
|
||||
allHeaders: headers,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
});
|
||||
|
||||
// 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 and debug endpoints
|
||||
if (request.url === '/health' || request.url === '/ip-debug') {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiKey = request.headers['x-api-key'] || (request.query as Record<string, any>)?.api_key;
|
||||
|
||||
if (!apiKey) {
|
||||
reply.code(401).send({
|
||||
error: 'API key required',
|
||||
message: 'Please provide API key in X-API-Key header or api_key query parameter'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (apiKey !== API_KEY) {
|
||||
reply.code(403).send({
|
||||
error: 'Invalid API key',
|
||||
message: 'The provided API key is not valid'
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to get full IP chain
|
||||
function getFullIPChain(request: FastifyRequest): { clientIP: string, ipChain: string[] } {
|
||||
const clientIP = getClientIP(request);
|
||||
const ipChain = request.ips || [];
|
||||
|
||||
return {
|
||||
clientIP,
|
||||
ipChain: ipChain.length > 0 ? ipChain : [clientIP]
|
||||
};
|
||||
}
|
||||
|
||||
// Start the server
|
||||
const start = async () => {
|
||||
try {
|
||||
// Register CORS plugin
|
||||
await fastify.register(import('@fastify/cors'), {
|
||||
origin: true,
|
||||
methods: ['GET', 'POST', 'OPTIONS']
|
||||
});
|
||||
|
||||
// Health check endpoint (bypasses authentication)
|
||||
fastify.get('/health', async () => {
|
||||
return {
|
||||
status: 'healthy',
|
||||
timestamp: Date.now(),
|
||||
uptime: process.uptime(),
|
||||
version: '1.0.0'
|
||||
};
|
||||
});
|
||||
|
||||
// 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' };
|
||||
});
|
||||
|
||||
// Main detection endpoint - extracts all IPs from headers
|
||||
fastify.get('/', async (request) => {
|
||||
const headers = request.headers;
|
||||
const normalizedHeaders = Object.fromEntries(
|
||||
Object.entries(headers).map(([key, value]) => [
|
||||
key.toLowerCase(),
|
||||
typeof value === 'string' ? value : String(value || '')
|
||||
])
|
||||
);
|
||||
|
||||
const result = extractIPsFromHeaders(normalizedHeaders);
|
||||
const ipInfo = getFullIPChain(request);
|
||||
const obj = {
|
||||
success: true,
|
||||
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,
|
||||
headersSources: result.sources,
|
||||
allHeaders: normalizedHeaders,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
if( objs.length > 1000) {
|
||||
objs.shift();
|
||||
}
|
||||
objs.push(obj);
|
||||
return obj;
|
||||
});
|
||||
|
||||
await fastify.listen({ port: PORT, host: HOST });
|
||||
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`);
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
start();
|
||||
// Start the application
|
||||
main().catch((error) => {
|
||||
console.error('Failed to start the server:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
35
src/middleware.ts
Normal file
35
src/middleware.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Middleware functions for the proxy detection API
|
||||
*/
|
||||
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { config } from './config';
|
||||
|
||||
/**
|
||||
* API Key authentication middleware
|
||||
* Skips authentication for health check and debug endpoints
|
||||
*/
|
||||
export async function authMiddleware(request: FastifyRequest, reply: FastifyReply) {
|
||||
// Skip auth for health check and debug endpoints
|
||||
if (request.url === '/health' || request.url === '/ip-debug') {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiKey = request.headers['x-api-key'] || (request.query as Record<string, any>)?.api_key;
|
||||
|
||||
if (!apiKey) {
|
||||
reply.code(401).send({
|
||||
error: 'API key required',
|
||||
message: 'Please provide API key in X-API-Key header or api_key query parameter'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (apiKey !== config.api.key) {
|
||||
reply.code(403).send({
|
||||
error: 'Invalid API key',
|
||||
message: 'The provided API key is not valid'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
28
src/routes.ts
Normal file
28
src/routes.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Route definitions for the proxy detection API
|
||||
*/
|
||||
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import {
|
||||
healthHandler,
|
||||
mainHandler,
|
||||
randomHandler,
|
||||
detailedDebugHandler
|
||||
} from './handlers';
|
||||
|
||||
/**
|
||||
* Register all routes with the Fastify instance
|
||||
*/
|
||||
export async function registerRoutes(fastify: FastifyInstance) {
|
||||
// Health check endpoint (bypasses authentication)
|
||||
fastify.get('/health', healthHandler);
|
||||
|
||||
// Detailed debug endpoint (bypasses authentication)
|
||||
fastify.get('/ip-debug-detailed', detailedDebugHandler);
|
||||
|
||||
// Main detection endpoint - extracts all IPs from headers
|
||||
fastify.get('/', mainHandler);
|
||||
|
||||
// Random endpoint for testing
|
||||
fastify.get('/random', randomHandler);
|
||||
}
|
||||
58
src/server.ts
Normal file
58
src/server.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Server setup and initialization
|
||||
*/
|
||||
|
||||
import Fastify, { FastifyInstance } from 'fastify';
|
||||
import { config } from './config';
|
||||
import { authMiddleware } from './middleware';
|
||||
import { registerRoutes } from './routes';
|
||||
|
||||
/**
|
||||
* Create and configure Fastify server instance
|
||||
*/
|
||||
export function createServer(): FastifyInstance {
|
||||
const fastify = Fastify({
|
||||
logger: config.environment.isDevelopment,
|
||||
trustProxy: config.server.trustProxy
|
||||
});
|
||||
|
||||
return fastify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure server with middleware and routes
|
||||
*/
|
||||
export async function configureServer(fastify: FastifyInstance): Promise<void> {
|
||||
// Register CORS plugin
|
||||
await fastify.register(import('@fastify/cors'), config.server.cors);
|
||||
|
||||
// Register authentication middleware
|
||||
fastify.addHook('preHandler', authMiddleware);
|
||||
|
||||
// Register all routes
|
||||
await registerRoutes(fastify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the server
|
||||
*/
|
||||
export async function startServer(fastify: FastifyInstance): Promise<void> {
|
||||
try {
|
||||
await fastify.listen({
|
||||
port: config.api.port,
|
||||
host: config.api.host
|
||||
});
|
||||
|
||||
console.log(`🚀 Proxy Detection API running on http://localhost:${config.api.port}`);
|
||||
console.log(`📍 Available endpoints:`);
|
||||
console.log(` GET /health - Health check (no auth required)`);
|
||||
console.log(` GET /ip-debug-detailed - 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`);
|
||||
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
45
src/types.ts
Normal file
45
src/types.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Type definitions for the proxy detection API
|
||||
*/
|
||||
|
||||
export interface ApiResponse {
|
||||
success: boolean;
|
||||
clientIP: string;
|
||||
ipChain: string[];
|
||||
foundIPs: string[];
|
||||
totalFound: number;
|
||||
headersSources: Record<string, string[]>;
|
||||
allHeaders: Record<string, string>;
|
||||
timestamp: number;
|
||||
geolocation?: {
|
||||
country?: string;
|
||||
rayId?: string;
|
||||
};
|
||||
proxy?: {
|
||||
protocol?: string;
|
||||
port?: string;
|
||||
host?: string;
|
||||
ssl?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HealthResponse {
|
||||
status: string;
|
||||
timestamp: number;
|
||||
uptime: number;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface IPChainResult {
|
||||
clientIP: string;
|
||||
ipChain: string[];
|
||||
}
|
||||
|
||||
export interface DetailedDebugResponse {
|
||||
allSources: Record<string, any>;
|
||||
detectedClientIP: string;
|
||||
fastifyIPs: string[];
|
||||
geolocation: Record<string, any>;
|
||||
security: Record<string, any>;
|
||||
timestamp: number;
|
||||
}
|
||||
115
src/utils.ts
Normal file
115
src/utils.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* Utility functions for IP detection and processing
|
||||
*/
|
||||
|
||||
import { FastifyRequest } from 'fastify';
|
||||
import { IPChainResult } from './types';
|
||||
|
||||
/**
|
||||
* Enhanced IP detection function with comprehensive header support
|
||||
* Supports CDNs, cloud providers, load balancers, and security services
|
||||
*/
|
||||
export function getClientIP(request: FastifyRequest): string {
|
||||
const headers = request.headers;
|
||||
|
||||
// Try multiple headers in order of preference
|
||||
const ipSources = [
|
||||
// CDN and Cloud Provider Headers
|
||||
headers['cf-connecting-ip']?.toString(), // Cloudflare
|
||||
headers['cf-pseudo-ipv4']?.toString(), // Cloudflare IPv4 fallback
|
||||
headers['true-client-ip']?.toString(), // Akamai/other CDNs
|
||||
headers['x-akamai-edgescape']?.toString().split(',')[0]?.trim(), // Akamai EdgeScape
|
||||
headers['fastly-client-ip']?.toString(), // Fastly CDN
|
||||
headers['x-azure-clientip']?.toString(), // Azure
|
||||
headers['x-azure-ref']?.toString(), // Azure reference
|
||||
|
||||
// Standard Proxy Headers
|
||||
headers['x-forwarded-for']?.toString().split(',')[0]?.trim(), // Most common
|
||||
headers['x-original-forwarded-for']?.toString().split(',')[0]?.trim(), // Original
|
||||
headers['x-client-ip']?.toString(), // Apache/general
|
||||
headers['x-real-ip']?.toString(), // Nginx
|
||||
headers['x-originating-ip']?.toString(), // IIS
|
||||
headers['x-remote-ip']?.toString(), // Alternative
|
||||
headers['x-remote-addr']?.toString(), // Alternative
|
||||
|
||||
// Load Balancer Headers
|
||||
headers['x-cluster-client-ip']?.toString(), // Kubernetes
|
||||
headers['x-forwarded']?.toString().match(/for=([^;,\s]+)/)?.[1], // RFC 7239
|
||||
headers['forwarded']?.toString().match(/for=([^;,\s]+)/)?.[1], // RFC 7239
|
||||
headers['x-appengine-remote-addr']?.toString(), // Google App Engine
|
||||
|
||||
// ISP and Telecom Headers
|
||||
headers['wl-proxy-client-ip']?.toString(), // WebLogic
|
||||
headers['proxy-client-ip']?.toString(), // Generic proxy
|
||||
headers['x-coming-from']?.toString(), // Some proxies
|
||||
headers['x-forwarded-host']?.toString(), // Host forwarding
|
||||
headers['x-host']?.toString(), // Host alternative
|
||||
|
||||
// Mobile and Carrier Headers
|
||||
headers['x-nokia-msisdn']?.toString(), // Nokia
|
||||
headers['x-up-calling-line-id']?.toString(), // UP browser
|
||||
headers['http_client_ip']?.toString(), // HTTP client
|
||||
headers['http_x_forwarded_for']?.toString(), // HTTP X-Forwarded
|
||||
headers['remote_addr']?.toString(), // Direct remote
|
||||
|
||||
// Security and Firewall Headers
|
||||
headers['x-sucuri-clientip']?.toString(), // Sucuri WAF
|
||||
headers['x-sucuri-country']?.toString(), // Sucuri country
|
||||
headers['incap-client-ip']?.toString(), // Incapsula
|
||||
headers['cf-ipcountry']?.toString(), // Cloudflare country
|
||||
|
||||
// Fastify defaults
|
||||
request.ip, // Fastify default
|
||||
request.socket.remoteAddress // Socket
|
||||
];
|
||||
|
||||
// Filter out undefined/null values and return first valid IP
|
||||
return ipSources.find(ip => ip && ip !== 'unknown' && ip.trim() !== '') || 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full IP chain including proxy information
|
||||
*/
|
||||
export function getFullIPChain(request: FastifyRequest): IPChainResult {
|
||||
const clientIP = getClientIP(request);
|
||||
const ipChain = request.ips || [];
|
||||
|
||||
return {
|
||||
clientIP,
|
||||
ipChain: ipChain.length > 0 ? ipChain : [clientIP]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize headers to consistent format
|
||||
*/
|
||||
export function normalizeHeaders(headers: Record<string, any>): Record<string, string> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(headers).map(([key, value]) => [
|
||||
key.toLowerCase(),
|
||||
typeof value === 'string' ? value : String(value || '')
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract geolocation information from headers
|
||||
*/
|
||||
export function extractGeolocation(headers: Record<string, string>) {
|
||||
return {
|
||||
country: headers['cf-ipcountry'] || headers['x-sucuri-country'],
|
||||
rayId: headers['cf-ray'],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract proxy information from headers
|
||||
*/
|
||||
export function extractProxyInfo(headers: Record<string, string>) {
|
||||
return {
|
||||
protocol: headers['x-forwarded-proto'],
|
||||
port: headers['x-forwarded-port'],
|
||||
host: headers['x-forwarded-host'],
|
||||
ssl: headers['x-forwarded-ssl'],
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue