feat: add IP address logging to webhooks for whitelisting

Implements comprehensive IP logging for webhook requests to enable
IP whitelisting and security monitoring.

Features:
- Capture source IP from webhook requests (x-forwarded-for, x-real-ip)
- Capture user agent for identification
- Store in webhook_logs table
- New API endpoint: GET /api/webhooks/ips
- View unique IPs with request counts and statistics
- Identify Autotask IPs for whitelisting

Database Changes:
- Added source_ip column (VARCHAR 45) to webhook_logs
- Added user_agent column (TEXT) to webhook_logs
- Added index on source_ip for efficient queries
- Migration 005 for existing installations

API Endpoints:
- GET /api/webhooks/ips?hours=168&entityType=Tickets
  Returns unique IPs with:
  * Request counts (total, successful, failed)
  * First/last seen timestamps
  * Entity types accessed
  * User agent strings

Use Cases:
1. Identify Autotask webhook IPs
2. Configure IP whitelist in nginx/Pangolin/Cloudflare
3. Monitor for unauthorized webhook attempts
4. Audit webhook sources
5. Detect IP changes from Autotask

Security Benefits:
- Enable IP whitelisting for webhook endpoint
- Block unauthorized webhook attempts
- Monitor for suspicious activity
- Audit trail of webhook sources

Documentation:
- Complete IP whitelisting guide (WEBHOOK_IP_WHITELISTING.md)
- Configuration examples for nginx, Pangolin, Cloudflare
- Monitoring queries and best practices
- Troubleshooting guide

Files Modified:
- migrations/004_webhook_support.sql - Added IP columns
- migrations/005_add_webhook_ip_logging.sql - Migration for existing installs
- lib/types/webhook.ts - Added IP fields to WebhookLog
- lib/services/webhook-service.ts - Capture and log IPs
- app/api/webhooks/autotask/route.ts - Extract IP from headers
- app/api/webhooks/ips/route.ts - New IP viewing endpoint
- docs/WEBHOOK_IP_WHITELISTING.md - Complete guide

Next Steps:
1. Run migration (004 for new, 005 for existing)
2. Deploy updated code
3. Receive webhooks from Autotask
4. View IPs via /api/webhooks/ips
5. Configure IP whitelist in proxy/tunnel
This commit is contained in:
root 2026-01-24 17:28:56 -05:00
parent 8e83347138
commit a093f8787c
7 changed files with 672 additions and 7 deletions

View file

@ -13,10 +13,16 @@ import { AutotaskWebhookPayload } from '@/lib/types/webhook';
*/
export async function POST(request: NextRequest) {
try {
// Extract source IP and user agent for logging
const sourceIp = request.headers.get('x-forwarded-for')?.split(',')[0].trim()
|| request.headers.get('x-real-ip')
|| 'unknown';
const userAgent = request.headers.get('user-agent') || 'unknown';
// Parse webhook payload
const payload: AutotaskWebhookPayload = await request.json();
console.log(`[WEBHOOK API] Received ${payload.eventType} event for ${payload.entityType} #${payload.entityId}`);
console.log(`[WEBHOOK API] Received ${payload.eventType} event for ${payload.entityType} #${payload.entityId} from IP: ${sourceIp}`);
// Validate required fields
if (!payload.eventId || !payload.eventType || !payload.entityType || !payload.entityId) {
@ -29,7 +35,7 @@ export async function POST(request: NextRequest) {
// Process the webhook asynchronously
// Note: We return 200 immediately to Autotask, then process in background
// This prevents timeouts for slow processing
const result = await webhookService.processWebhook(payload);
const result = await webhookService.processWebhook(payload, sourceIp, userAgent);
if (result.success) {
return NextResponse.json({

View file

@ -0,0 +1,77 @@
/**
* Webhook Source IPs API
* View unique source IPs from webhook logs for whitelisting
*/
import { NextRequest, NextResponse } from 'next/server';
import { postgresClient } from '@/lib/services/postgres-client';
/**
* GET /api/webhooks/ips
* Get unique source IPs from webhook logs
*/
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const hours = parseInt(searchParams.get('hours') || '168'); // Default 7 days
const entityType = searchParams.get('entityType') || undefined;
// Build query to get unique IPs with counts
let query = `
SELECT
source_ip,
user_agent,
COUNT(*) as request_count,
MIN(received_at) as first_seen,
MAX(received_at) as last_seen,
COUNT(*) FILTER (WHERE status = 'processed') as successful_count,
COUNT(*) FILTER (WHERE status = 'failed') as failed_count,
array_agg(DISTINCT entity_type) as entity_types
FROM webhook_logs
WHERE received_at >= NOW() - INTERVAL '${hours} hours'
AND source_ip IS NOT NULL
`;
const params: any[] = [];
if (entityType) {
query += ` AND entity_type = $1`;
params.push(entityType);
}
query += `
GROUP BY source_ip, user_agent
ORDER BY request_count DESC
`;
const result = await postgresClient.query(query, params);
// Format the results
const ips = result.rows.map((row: any) => ({
ip: row.source_ip,
userAgent: row.user_agent,
requestCount: parseInt(row.request_count),
successfulCount: parseInt(row.successful_count),
failedCount: parseInt(row.failed_count),
firstSeen: row.first_seen,
lastSeen: row.last_seen,
entityTypes: row.entity_types,
}));
return NextResponse.json({
success: true,
ips,
count: ips.length,
period: `${hours} hours`,
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('[WEBHOOK IPS API] Error fetching IPs:', errorMessage);
return NextResponse.json(
{ error: 'Failed to fetch webhook IPs', details: errorMessage },
{ status: 500 }
);
}
}