/** * API Proxy for PCA Platform (Heuristic Service) * * Provides secure server-side access to the PCA Heuristic Service * for bot detection, session monitoring, and configuration management * * Heuristic Service runs on port 8085 */ import { NextRequest, NextResponse } from 'next/server' const PCA_SERVICE_URL = process.env.PCA_SERVICE_URL || 'http://localhost:8085' // Helper to make fetch with timeout and error handling async function safeFetch(url: string, options?: RequestInit): Promise { try { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 5000) const response = await fetch(url, { ...options, signal: controller.signal, }) clearTimeout(timeoutId) return response } catch { return null } } // GET: Fetch health, sessions, config, or stats export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams const action = searchParams.get('action') // Health check if (action === 'health') { const response = await safeFetch(`${PCA_SERVICE_URL}/health`, { headers: { 'Content-Type': 'application/json' }, }) if (!response || !response.ok) { return NextResponse.json({ status: 'offline', message: 'PCA Heuristic Service not available', }) } try { const data = await response.json() return NextResponse.json({ status: 'healthy', ...data, }) } catch { return NextResponse.json({ status: 'healthy' }) } } // Get client config if (action === 'config') { const response = await safeFetch(`${PCA_SERVICE_URL}/pca/v1/config`, { headers: { 'Content-Type': 'application/json' }, }) if (response && response.ok) { try { const data = await response.json() return NextResponse.json(data) } catch { // Fall through to default } } // Return default config if service unavailable or error return NextResponse.json({ tick: { endpoint: '/pca/v1/tick', interval_ms: 5000 }, thresholds: { score_pass: 0.7, score_challenge: 0.4 }, weights: { dwell_ratio: 0.30, scroll_score: 0.25, pointer_variance: 0.20, click_rate: 0.25, }, step_up: { methods: ['webauthn', 'pow'], primary: 'webauthn' }, service_status: 'offline', }) } // Get admin stats if (action === 'stats') { const response = await safeFetch(`${PCA_SERVICE_URL}/pca/v1/admin/stats`, { headers: { 'Content-Type': 'application/json' }, }) if (response && response.ok) { try { const data = await response.json() return NextResponse.json(data) } catch { // Fall through to default } } // Return mock stats if service unavailable return NextResponse.json({ active_sessions: 0, total_ticks: 0, challenges_issued: 0, challenges_passed: 0, avg_score: 0, humans_detected: 0, bots_detected: 0, service_status: 'offline', }) } // Get active sessions if (action === 'sessions') { const response = await safeFetch(`${PCA_SERVICE_URL}/pca/v1/admin/sessions`, { headers: { 'Content-Type': 'application/json' }, }) if (response && response.ok) { try { const data = await response.json() return NextResponse.json(data) } catch { // Fall through to default } } // Return empty sessions if service unavailable return NextResponse.json({ sessions: [], total: 0, service_status: 'offline', }) } // Get ai-access.json configuration if (action === 'ai-access') { const response = await safeFetch(`${PCA_SERVICE_URL}/pca/v1/admin/ai-access`, { headers: { 'Content-Type': 'application/json' }, }) if (response && response.ok) { try { const data = await response.json() return NextResponse.json(data) } catch { // Fall through to default } } // Return default ai-access config return NextResponse.json({ version: '1.0', thresholds: { score_pass: 0.7, score_challenge: 0.4 }, weights: { dwell_ratio: 0.30, scroll_score: 0.25, pointer_variance: 0.20, click_rate: 0.25, }, step_up: { methods: ['webauthn', 'pow'], primary: 'webauthn' }, pca_roles: { Person: { access: 'allow', price: null }, Corporate: { access: 'allow', price: '0.01 EUR' }, Agent: { access: 'charge', price: '0.001 EUR' }, }, payment: { enabled: false }, service_status: 'offline', }) } // Default: health check const response = await safeFetch(`${PCA_SERVICE_URL}/health`, { headers: { 'Content-Type': 'application/json' }, }) if (!response || !response.ok) { return NextResponse.json({ status: 'offline', service: 'pca-heuristic-service', }) } try { const data = await response.json() return NextResponse.json(data) } catch { return NextResponse.json({ status: 'offline', service: 'pca-heuristic-service', }) } } // POST: Evaluate session or update config export async function POST(request: NextRequest) { const searchParams = request.nextUrl.searchParams const action = searchParams.get('action') const body = await request.json().catch(() => ({})) // Evaluate a specific session if (action === 'evaluate') { const sessionId = searchParams.get('sessionId') if (!sessionId) { return NextResponse.json({ error: 'Session ID required' }, { status: 400 }) } const response = await safeFetch( `${PCA_SERVICE_URL}/pca/v1/evaluate?session_id=${sessionId}`, { headers: { 'Content-Type': 'application/json' } } ) if (!response || !response.ok) { return NextResponse.json( { error: 'Failed to evaluate session - service offline' }, { status: 503 } ) } try { const data = await response.json() return NextResponse.json(data) } catch { return NextResponse.json( { error: 'Invalid response from service' }, { status: 500 } ) } } // Update configuration (admin only) if (action === 'update-config') { const response = await safeFetch(`${PCA_SERVICE_URL}/pca/v1/admin/config`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) if (!response || !response.ok) { return NextResponse.json( { error: 'Failed to update configuration - service offline' }, { status: 503 } ) } try { const data = await response.json() return NextResponse.json(data) } catch { return NextResponse.json({ success: true }) } } // Clear session (admin only) if (action === 'clear-session') { const sessionId = searchParams.get('sessionId') if (!sessionId) { return NextResponse.json({ error: 'Session ID required' }, { status: 400 }) } const response = await safeFetch( `${PCA_SERVICE_URL}/pca/v1/admin/sessions/${sessionId}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, } ) if (!response || !response.ok) { return NextResponse.json( { error: 'Failed to clear session - service offline' }, { status: 503 } ) } return NextResponse.json({ deleted: true, session_id: sessionId }) } return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) }