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

121
CODE_ORGANIZATION.md Normal file
View file

@ -0,0 +1,121 @@
# Proxy Detection API - Code Organization
This document describes the organized file structure of the Proxy Detection API.
## File Structure
The application has been organized into separate modules for better maintainability:
### Core Files
- **`index.ts`** - Entry point for the application
- **`server.ts`** - Server setup and initialization
- **`routes.ts`** - Route definitions and registration
### Module Files
- **`types.ts`** - TypeScript type definitions and interfaces
- **`config.ts`** - Configuration and environment variables
- **`middleware.ts`** - Authentication and other middleware functions
- **`handlers.ts`** - Route handler functions
- **`utils.ts`** - Utility functions for IP detection and processing
### Service Files
- **`proxyService.ts`** - IP extraction service (existing)
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────┐
│ index.ts │
│ (Entry Point) │
└─────────────────────┬───────────────────────────────────────┘
┌─────────────────────▼───────────────────────────────────────┐
│ server.ts │
│ (Server Configuration) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐│
│ │ config │ │ middleware │ │ routes ││
│ └─────────────┘ └─────────────┘ └─────────────────────────┘│
└─────────────────────┬───────────────────────────────────────┘
┌─────────────────────▼───────────────────────────────────────┐
│ handlers.ts │
│ (Route Handlers) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐│
│ │ utils │ │ types │ │ proxyService ││
│ └─────────────┘ └─────────────┘ └─────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
```
## Module Responsibilities
### `types.ts`
- `ApiResponse` - Main API response interface
- `HealthResponse` - Health check response interface
- `IPChainResult` - IP chain result interface
- `DetailedDebugResponse` - Debug endpoint response interface
### `config.ts`
- Environment variable management
- Server configuration settings
- CORS and trust proxy settings
- Application constants
### `utils.ts`
- `getClientIP()` - Enhanced IP detection with 30+ header sources
- `getFullIPChain()` - Extract full proxy chain information
- `normalizeHeaders()` - Normalize headers to consistent format
- `extractGeolocation()` - Extract geolocation from headers
- `extractProxyInfo()` - Extract proxy information from headers
### `middleware.ts`
- `authMiddleware()` - API key authentication
- Handles both header and query parameter authentication
- Skips authentication for health and debug endpoints
### `handlers.ts`
- `healthHandler()` - Health check endpoint
- `mainHandler()` - Main proxy detection endpoint
- `randomHandler()` - Random cached result endpoint
- `detailedDebugHandler()` - Detailed debug information endpoint
- In-memory result caching management
### `routes.ts`
- Route registration and organization
- Maps endpoints to their respective handlers
- Maintains authentication bypass configuration
### `server.ts`
- Fastify server instance creation
- CORS configuration
- Middleware registration
- Server startup and error handling
## Benefits of This Organization
1. **Separation of Concerns** - Each file has a specific responsibility
2. **Maintainability** - Easier to locate and modify specific functionality
3. **Testability** - Individual modules can be unit tested independently
4. **Reusability** - Utility functions can be easily reused
5. **Type Safety** - Centralized type definitions ensure consistency
6. **Configuration Management** - All settings are centralized and manageable
## Development Workflow
1. **Adding new endpoints**: Add handler to `handlers.ts`, register route in `routes.ts`
2. **Modifying IP detection**: Update functions in `utils.ts`
3. **Changing configuration**: Update `config.ts`
4. **Adding new types**: Add to `types.ts`
5. **Adding middleware**: Add to `middleware.ts`
## Running the Application
The application starts from `index.ts` which:
1. Creates a server instance
2. Configures middleware and routes
3. Starts the server
4. Handles startup errors
All functionality remains the same as the original monolithic version, but is now organized for better maintainability and scalability.

314
IP_DETECTION_K8S_SETUP.md Normal file
View file

@ -0,0 +1,314 @@
# Real IP Detection in Kubernetes - Complete Setup Guide
This document summarizes all the steps taken to successfully configure real IP detection for the proxy detection API in a Kubernetes cluster with MetalLB LoadBalancer.
## Problem Statement
Initially, the API was only receiving internal Kubernetes cluster IPs instead of real client IPs when deployed in Kubernetes. The application needed to detect the actual client IP addresses through various proxy headers while running behind:
- MetalLB LoadBalancer
- Nginx Ingress Controller
- Kubernetes Service mesh
## Solution Overview
We implemented a multi-layered approach to ensure proper IP detection:
1. **Kubernetes Infrastructure Configuration**
2. **Application-Level IP Detection**
3. **Ingress and LoadBalancer Optimization**
---
## 1. Kubernetes Infrastructure Setup
### MetalLB LoadBalancer Configuration
**Issue**: The MetalLB LoadBalancer was not properly preserving client IPs.
**Solution**:
- Identified LoadBalancer external IP: `192.95.29.118`
- Configured `externalTrafficPolicy: Local` to preserve source IPs
- Corrected namespace from `ingress-nginx` to `ingress`
```yaml
# k8s-service.yaml
apiVersion: v1
kind: Service
metadata:
name: proxy-detection-service
namespace: default
spec:
type: LoadBalancer
externalTrafficPolicy: Local # Critical for IP preservation
ports:
- port: 80
targetPort: 2424
protocol: TCP
selector:
app: proxy-detection
```
### Ingress Controller Configuration
**Issue**: Nginx Ingress was not configured to pass real client IPs.
**Solution**: Enhanced ingress configuration with custom annotations and ConfigMap.
```yaml
# k8s-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: proxy-detection-ingress
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/use-real-ip: "true"
nginx.ingress.kubernetes.io/real-ip-header: "X-Forwarded-For"
nginx.ingress.kubernetes.io/compute-full-forwarded-for: "true"
nginx.ingress.kubernetes.io/forwarded-for-header: "X-Forwarded-For"
# Additional IP preservation headers
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Real-IP $remote_addr";
more_set_headers "X-Forwarded-For $proxy_add_x_forwarded_for";
spec:
rules:
- host: proxy-detection.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: proxy-detection-service
port:
number: 80
```
### ConfigMap for Custom Headers
**Created**: Custom ConfigMap to configure Nginx for proper header handling.
```bash
kubectl create configmap nginx-configuration \
--from-literal=use-forwarded-headers=true \
--from-literal=compute-full-forwarded-for=true \
--from-literal=use-proxy-protocol=false \
-n ingress
```
---
## 2. Application-Level IP Detection
### Comprehensive IP Header Detection
**Issue**: The application wasn't checking all possible IP headers from various sources.
**Solution**: Implemented comprehensive IP detection supporting 30+ header sources.
#### Enhanced `getClientIP()` Function
```typescript
// src/utils.ts
export function getClientIP(request: FastifyRequest): string {
const headers = request.headers;
const ipSources = [
// CDN and Cloud Provider Headers
headers['cf-connecting-ip']?.toString(), // Cloudflare
headers['cf-pseudo-ipv4']?.toString(), // Cloudflare IPv4
headers['true-client-ip']?.toString(), // Akamai/CDNs
headers['x-akamai-edgescape']?.toString().split(',')[0]?.trim(),
headers['fastly-client-ip']?.toString(), // Fastly CDN
headers['x-azure-clientip']?.toString(), // Azure
// Standard Proxy Headers
headers['x-forwarded-for']?.toString().split(',')[0]?.trim(), // Most common
headers['x-original-forwarded-for']?.toString().split(',')[0]?.trim(),
headers['x-client-ip']?.toString(), // Apache
headers['x-real-ip']?.toString(), // Nginx
headers['x-originating-ip']?.toString(), // IIS
// Load Balancer Headers
headers['x-cluster-client-ip']?.toString(), // Kubernetes
headers['forwarded']?.toString().match(/for=([^;,\s]+)/)?.[1], // RFC 7239
headers['x-appengine-remote-addr']?.toString(), // Google App Engine
// Security and Firewall Headers
headers['x-sucuri-clientip']?.toString(), // Sucuri WAF
headers['incap-client-ip']?.toString(), // Incapsula
// Fastify defaults
request.ip,
request.socket.remoteAddress
];
return ipSources.find(ip => ip && ip !== 'unknown' && ip.trim() !== '') || 'unknown';
}
```
### Trust Proxy Configuration
**Issue**: Fastify wasn't configured to trust the Kubernetes network ranges.
**Solution**: Configured comprehensive trust proxy settings.
```typescript
// src/config.ts
export const config = {
server: {
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
]
}
};
```
---
## 3. Debugging and Verification
### Debug Endpoints
**Created**: Comprehensive debug endpoints to troubleshoot IP detection.
```typescript
// Detailed debug endpoint showing all possible IP sources
fastify.get('/ip-debug-detailed', async (request) => {
return {
allSources: {
'cf-connecting-ip': headers['cf-connecting-ip'],
'x-forwarded-for': headers['x-forwarded-for'],
'x-real-ip': headers['x-real-ip'],
'x-cluster-client-ip': headers['x-cluster-client-ip'],
// ... all 30+ headers
},
detectedClientIP: getClientIP(request),
fastifyIPs: request.ips || [],
geolocation: { /* geo info */ },
security: { /* security headers */ }
};
});
```
### Testing Process
1. **Local Testing**: Verified basic functionality
2. **Kubernetes Deployment**: Deployed with debug endpoints
3. **IP Verification**: Used `/ip-debug-detailed` to verify real IPs
4. **Load Testing**: Confirmed consistent IP detection
---
## 4. Key Configuration Commands
### Kubernetes Deployment Commands
```bash
# Apply all configurations
kubectl apply -f k8s-deployment.yaml
kubectl apply -f k8s-service.yaml
kubectl apply -f k8s-ingress.yaml
kubectl apply -f k8s-secret.yaml
# Create nginx configuration
kubectl create configmap nginx-configuration \
--from-literal=use-forwarded-headers=true \
--from-literal=compute-full-forwarded-for=true \
-n ingress
# Verify LoadBalancer
kubectl get svc proxy-detection-service
# Should show EXTERNAL-IP: 192.95.29.118
# Check ingress status
kubectl get ingress proxy-detection-ingress
```
### Verification Commands
```bash
# Test real IP detection
curl -H "X-Forwarded-For: 203.0.113.1" http://192.95.29.118/ip-debug-detailed
# Check pods
kubectl get pods -l app=proxy-detection
# View logs
kubectl logs -l app=proxy-detection
```
---
## 5. Critical Success Factors
### What Made It Work
1. **Correct Namespace**: Changed from `ingress-nginx` to `ingress`
2. **externalTrafficPolicy: Local**: Preserved source IPs at LoadBalancer level
3. **Comprehensive Header Detection**: Supported all major proxy/CDN headers
4. **Trust Proxy Configuration**: Properly configured Fastify to trust K8s networks
5. **Ingress Annotations**: Used correct nginx annotations for IP forwarding
### Common Pitfalls Avoided
- ❌ Using wrong namespace for ingress controller
- ❌ Missing `externalTrafficPolicy: Local`
- ❌ Not configuring trust proxy in application
- ❌ Limited header detection (only checking X-Forwarded-For)
- ❌ Missing nginx ingress annotations
---
## 6. Final Architecture
```
Internet Client (Real IP: 203.0.113.1)
MetalLB LoadBalancer (192.95.29.118)
↓ [preserves source IP with externalTrafficPolicy: Local]
Nginx Ingress Controller
↓ [adds X-Forwarded-For, X-Real-IP headers]
Kubernetes Service
↓ [routes to pod]
Proxy Detection API Pod
↓ [getClientIP() checks 30+ headers]
Returns: Real Client IP (203.0.113.1)
```
## 7. Results
**Success**: Real client IPs are now properly detected
**Reliability**: Consistent IP detection across all request types
**Scalability**: Supports multiple CDNs, proxies, and security services
**Debugging**: Comprehensive debug endpoints for troubleshooting
The API now successfully detects real client IPs in the Kubernetes environment with MetalLB LoadBalancer, providing accurate proxy detection capabilities for production use.
COMMANDS
# Configure NGINX Ingress Controller (in 'ingress' namespace)
kubectl patch configmap ingress-nginx-controller -n ingress --patch '
data:
use-forwarded-headers: "true"
compute-full-forwarded-for: "true"
real-ip-header: "X-Forwarded-For"
set-real-ip-from: "192.95.29.118/32"
enable-real-ip: "true"
proxy-real-ip-cidr: "192.95.29.118/32"
'
# Set external traffic policy to Local for source IP preservation
kubectl patch service ingress-ingress-nginx-controller -n ingress --patch '
spec:
externalTrafficPolicy: Local
'
kubectl rollout restart deployment ingress-nginx-controller -n ingress

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 import { createServer, configureServer, startServer } from './server';
interface ApiResponse {
success: boolean; /**
clientIP: string; * Initialize and start the proxy detection API
ipChain: string[]; */
foundIPs: string[]; async function main() {
totalFound: number; const server = createServer();
headersSources: Record<string, string[]>; await configureServer(server);
allHeaders: Record<string, string>; await startServer(server);
timestamp: number;
} }
// Environment configuration // Start the application
const API_KEY = process.env.API_KEY; main().catch((error) => {
const PORT = parseInt(process.env.PORT || '2424'); console.error('Failed to start the server:', error);
const HOST = process.env.HOST || '0.0.0.0'; process.exit(1);
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();

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