From 16c40ddae457c47cbf1fd4a371ba655189bb9466 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Wed, 29 Apr 2026 13:14:01 +0200 Subject: [PATCH] feat: consent-test email with phase-structured findings Email shows 3 phases (Before/After Reject/After Accept) with: - Violation cards per phase (CRITICAL/HIGH badges) - Undocumented services in Phase C - Summary table (critical/high/undocumented counts) - Dark Pattern warning if tracking persists after rejection Co-Authored-By: Claude Opus 4.6 (1M context) --- .../api/sdk/v1/agent/consent-test/route.ts | 115 +++++++++++++++++- 1 file changed, 110 insertions(+), 5 deletions(-) diff --git a/admin-compliance/app/api/sdk/v1/agent/consent-test/route.ts b/admin-compliance/app/api/sdk/v1/agent/consent-test/route.ts index 4440458..92b7ed7 100644 --- a/admin-compliance/app/api/sdk/v1/agent/consent-test/route.ts +++ b/admin-compliance/app/api/sdk/v1/agent/consent-test/route.ts @@ -1,21 +1,105 @@ /** * Consent Test API Proxy - * POST /api/sdk/v1/agent/consent-test → consent-tester:8094/scan + * POST /api/sdk/v1/agent/consent-test → consent-tester:8094/scan → email via backend */ import { NextRequest, NextResponse } from 'next/server' const CONSENT_TESTER_URL = process.env.CONSENT_TESTER_URL || 'http://bp-compliance-consent-tester:8094' +const BACKEND_URL = process.env.BACKEND_API_URL || 'http://backend-compliance:8002' + +interface Violation { service: string; severity: string; text: string; legal_ref: string } + +function buildEmailHtml(data: any): string { + const url = data.url || '' + const banner = data.banner_detected ? data.banner_provider : 'Nicht erkannt' + const phases = data.phases || {} + const summary = data.summary || {} + + const sev = (s: string) => s === 'CRITICAL' + ? 'KRITISCH' + : 'HOCH' + + const violationRows = (violations: Violation[]) => violations.length === 0 + ? '✓ Keine Verstoesse' + : violations.map(v => + `${sev(v.severity)}${v.service}${v.text}
${v.legal_ref}` + ).join('') + + const undocRows = (items: string[]) => items.length === 0 + ? '' + : items.map(s => `⚠${s}Nicht in Cookie-Policy dokumentiert`).join('') + + return ` +
+
+

Cookie-Consent-Test

+

${url}

+
+ +
+ + + + + +
Cookie-Banner${data.banner_detected ? '✓ ' + banner : '✗ Nicht erkannt'}
Kritische Verstoesse${summary.critical || 0}
Hohe Verstoesse${summary.high || 0}
Undokumentiert${summary.undocumented || 0}
+ +

+ 🔍 Phase A: Vor Einwilligung +

+

Was laedt OHNE dass der Nutzer etwas geklickt hat?

+ ${violationRows(phases.before_consent?.violations || [])}
+ + ${data.banner_detected ? ` +

+ 🚫 Phase B: Nach Ablehnung +

+

Was laedt NACHDEM der Nutzer "Nur notwendige" geklickt hat?

+ ${violationRows(phases.after_reject?.violations || [])}
+ +

+ ✅ Phase C: Nach Zustimmung +

+

Was laedt NACHDEM der Nutzer "Alle akzeptieren" geklickt hat?

+ ${undocRows(phases.after_accept?.undocumented || [])}
+ ${(phases.after_accept?.undocumented?.length || 0) === 0 ? '

✓ Alle Dienste dokumentiert

' : ''} + ` : ` +
+ Kein Cookie-Banner erkannt. + Alle Tracking-Dienste laden ohne Einwilligung — Verstoss gegen §25 TDDDG. +
+ `} + + ${(summary.critical || 0) > 0 ? ` +
+ ⚠ KRITISCH: Tracking-Dienste laden trotz Ablehnung. + Dies ist ein schwerer Verstoss gegen §25 TDDDG und kann als Dark Pattern gewertet werden. + Sofortige Korrektur der Cookie-Banner-Konfiguration empfohlen. +
+ ` : ''} +
+ +
+

+ Automatisch erstellt vom BreakPilot Compliance Agent (Playwright + Chromium) +

+
+
+ ` +} export async function POST(request: NextRequest) { try { - const body = await request.text() + const body = await request.json() + const url = body.url + // Step 1: Run consent test const response = await fetch(`${CONSENT_TESTER_URL}/scan`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body, - signal: AbortSignal.timeout(180000), // 3 min — 3 browser phases + body: JSON.stringify(body), + signal: AbortSignal.timeout(180000), }) if (!response.ok) { @@ -26,7 +110,28 @@ export async function POST(request: NextRequest) { ) } - return NextResponse.json(await response.json()) + const data = await response.json() + + // Step 2: Send email with phase-structured findings + try { + const total = (data.summary?.total_violations || 0) + const severity = (data.summary?.critical || 0) > 0 ? 'KRITISCH' : total > 0 ? 'FINDINGS' : 'OK' + await fetch(`${BACKEND_URL}/api/compliance/agent/notify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + recipient: body.recipient || 'dsb@breakpilot.local', + subject: `[COOKIE-TEST] [${severity}] ${url} — ${total} Verstoesse`, + body_html: buildEmailHtml({ ...data, url }), + role: total > 0 ? 'Datenschutzbeauftragter' : 'Kein Handlungsbedarf', + }), + signal: AbortSignal.timeout(10000), + }) + } catch (emailErr) { + console.warn('Email send failed (non-blocking):', emailErr) + } + + return NextResponse.json(data) } catch (error) { console.error('Consent test proxy error:', error) return NextResponse.json(