This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/website/app/api/admin/pca/route.ts
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

288 lines
7.5 KiB
TypeScript

/**
* 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<Response | null> {
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 })
}