reogranized app and added more headers

This commit is contained in:
Bojan Kucera 2025-06-05 23:10:18 -04:00
parent 1048ab5c20
commit 223f426a58
10 changed files with 925 additions and 218 deletions

35
src/config.ts Normal file
View 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
View 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()
};
}

View file

@ -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
View 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
View 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
View 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
View 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
View 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'],
};
}