import { angemeldeterBenutzer } from '@/lib/auth/sitzung'; import { darfAzubiDatenSehen } from '@/lib/auth/regeln'; import { ausbildungVonAzubi } from '@/lib/auth/rechte'; import { anhangLesen, anhangNachId } from '@/lib/repos/anhaenge'; import { istBild } from '@/lib/anhaenge/dateien'; import { modulAn, modulGesperrt } from '@/lib/module/pruefen'; export const dynamic = 'force-dynamic'; /** * Liefert einen Anhang aus. * * Zwei Dinge sind hier wichtig: * 1. Der Pfad kommt ausschließlich aus der Datenbank. Es gibt keinen Weg, * über die Adresse eine beliebige Datei anzufordern. * 2. Die Berechtigung wird bei **jedem** Abruf geprüft. Wer den Anhang * hochgeladen hat, darf ihn sehen; sein Ausbilder auch, sonst niemand. */ export async function GET(_anfrage: Request, kontext: { params: Promise<{ id: string }> }) { const benutzer = await angemeldeterBenutzer(); if (!benutzer) return new Response('Nicht angemeldet.', { status: 401 }); if (!modulAn('aufgaben')) return modulGesperrt(); const { id } = await kontext.params; const eintrag = anhangNachId(id); if (!eintrag) return new Response('Nicht gefunden.', { status: 404 }); const bezug = ausbildungVonAzubi(eintrag.benutzerId); if (!darfAzubiDatenSehen(benutzer, eintrag.benutzerId, bezug)) { // Bewusst 404 statt 403: Ob es diesen Anhang gibt, geht Fremde nichts an. return new Response('Nicht gefunden.', { status: 404 }); } const inhalt = await anhangLesen(eintrag); if (!inhalt) return new Response('Die Datei fehlt im Speicher.', { status: 410 }); // Bilder und PDF dürfen im Browser aufgehen, alles andere wird geladen. const anzeigbar = istBild(eintrag.mimeTyp) || eintrag.mimeTyp === 'application/pdf'; const art = anzeigbar ? 'inline' : 'attachment'; return new Response(new Uint8Array(inhalt), { headers: { 'Content-Type': eintrag.mimeTyp, 'Content-Length': String(inhalt.byteLength), 'Content-Disposition': `${art}; filename="${eintrag.dateiname}"; filename*=UTF-8''${encodeURIComponent(eintrag.dateiname)}`, // Kein öffentlicher Zwischenspeicher – der Inhalt hängt am angemeldeten Benutzer. 'Cache-Control': 'private, max-age=300', 'X-Content-Type-Options': 'nosniff', // Schutz davor, dass ein hochgeladenes SVG oder HTML im Kontext der // Anwendung ausgeführt wird. Anhänge sind Daten, kein Programm. 'Content-Security-Policy': "default-src 'none'; img-src 'self'; style-src 'unsafe-inline'; sandbox", }, }); }