basic proxy ip detection test tool
This commit is contained in:
commit
d3cff15545
15 changed files with 1075 additions and 0 deletions
134
src/index.ts
Normal file
134
src/index.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { extractIPsFromHeaders } from './proxyService';
|
||||
|
||||
// Type definitions
|
||||
interface ApiResponse {
|
||||
success: boolean;
|
||||
clientIP: string;
|
||||
ipChain: string[];
|
||||
foundIPs: string[];
|
||||
totalFound: number;
|
||||
headersSources: Record<string, string[]>;
|
||||
allHeaders: Record<string, string>;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
// 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 trust proxy enabled
|
||||
const fastify = Fastify({
|
||||
logger: NODE_ENV === 'development',
|
||||
trustProxy: process.env.TRUST_PROXY === 'true' || true
|
||||
});
|
||||
|
||||
// API Key authentication middleware
|
||||
fastify.addHook('preHandler', async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
// Skip auth for health check endpoint
|
||||
if (request.url === '/health') {
|
||||
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[] } {
|
||||
// Fastify automatically parses X-Forwarded-For when trustProxy is enabled
|
||||
const clientIP = request.ip || 'unknown';
|
||||
const ipChain = request.ips || []; // Full chain of 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'
|
||||
};
|
||||
});
|
||||
|
||||
// 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, // Fastify's auto-detected client IP
|
||||
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 / - 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();
|
||||
100
src/proxyService.ts
Normal file
100
src/proxyService.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
export interface HeaderIPResult {
|
||||
ips: string[];
|
||||
sources: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export function extractIPsFromHeaders(headers: Record<string, string | undefined>): HeaderIPResult {
|
||||
const ips: string[] = [];
|
||||
const sources: Record<string, string[]> = {};
|
||||
|
||||
// Common headers that might contain IP addresses
|
||||
const ipHeaders = [
|
||||
'x-forwarded-for',
|
||||
'x-real-ip',
|
||||
'x-client-ip',
|
||||
'x-cluster-client-ip',
|
||||
'x-forwarded',
|
||||
'forwarded-for',
|
||||
'forwarded',
|
||||
'cf-connecting-ip',
|
||||
'true-client-ip',
|
||||
'x-originating-ip',
|
||||
'x-remote-ip',
|
||||
'x-remote-addr',
|
||||
'client-ip',
|
||||
'remote-addr',
|
||||
'http-client-ip',
|
||||
'http-x-forwarded-for',
|
||||
'http-x-real-ip'
|
||||
];
|
||||
|
||||
// IP regex pattern (basic IPv4 validation)
|
||||
const ipRegex = /\b(?:(?: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]?)\b/g;
|
||||
|
||||
// Check each header for IP addresses
|
||||
for (const [headerName, headerValue] of Object.entries(headers)) {
|
||||
if (!headerValue) continue;
|
||||
|
||||
// Check if this is a known IP header
|
||||
if (ipHeaders.includes(headerName.toLowerCase())) {
|
||||
const foundIPs = extractIPsFromValue(headerValue);
|
||||
if (foundIPs.length > 0) {
|
||||
sources[headerName] = foundIPs;
|
||||
ips.push(...foundIPs);
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for IP patterns in any header value
|
||||
const matches = headerValue.match(ipRegex);
|
||||
if (matches) {
|
||||
const validIPs = matches.filter(isValidIP);
|
||||
if (validIPs.length > 0) {
|
||||
if (!sources[headerName]) {
|
||||
sources[headerName] = [];
|
||||
}
|
||||
sources[headerName].push(...validIPs);
|
||||
ips.push(...validIPs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates while preserving order
|
||||
const uniqueIPs = [...new Set(ips)];
|
||||
|
||||
return {
|
||||
ips: uniqueIPs,
|
||||
sources
|
||||
};
|
||||
}
|
||||
|
||||
function extractIPsFromValue(value: string): string[] {
|
||||
const ips: string[] = [];
|
||||
|
||||
// Handle comma-separated values (common in X-Forwarded-For)
|
||||
const parts = value.split(',').map(part => part.trim());
|
||||
|
||||
for (const part of parts) {
|
||||
// Handle "for=" syntax in Forwarded header
|
||||
if (part.startsWith('for=')) {
|
||||
const ip = part.substring(4).replace(/"/g, '');
|
||||
if (isValidIP(ip)) {
|
||||
ips.push(ip);
|
||||
}
|
||||
} else if (isValidIP(part)) {
|
||||
ips.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
return ips;
|
||||
}
|
||||
|
||||
function isValidIP(ip: string): boolean {
|
||||
// Basic IPv4 validation
|
||||
const parts = ip.split('.');
|
||||
if (parts.length !== 4) return false;
|
||||
|
||||
return parts.every(part => {
|
||||
const num = parseInt(part, 10);
|
||||
return !isNaN(num) && num >= 0 && num <= 255;
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue