Files
opencrm/frontend/src/utils/fileUrl.ts
T
duffyduckandClaude Opus 4.8 7d062bf9b2 PdfDragButton R131: 60s-Download-Token statt Access-Token, nur DownloadURL
Pentest R131 (LOW-MEDIUM): Der Drag-Button haengte den 15-Min-Access-
Token an die URL und schrieb ihn per text/plain + text/uri-list beim
Drag mit. Ein Fehl-Drop in ein Text-/Chat-/URL-Feld haette den vollen
Access-Token (alle Berechtigungen, 15 Min) als lesbaren Text geleakt.

Fix:
- fileUrl() akzeptiert jetzt optionalen expliziten Token.
- PdfDragButton nutzt den kurzlebigen 60s-Download-Token
  (authApi.getDownloadToken(), type:download, nur ?token=) statt des
  Access-Tokens. Modul-weiter Cache mit Dedup, auf mount + hover
  vorgewaermt (dragstart ist synchron, kann nicht awaiten).
- Es wird NUR noch DownloadURL im DataTransfer gesetzt, keine
  text/plain- oder text/uri-list-Repraesentation -> Fehl-Drop in ein
  Textfeld erzeugt gar keinen sichtbaren Text.
- Klick-Vorschau nutzt ebenfalls bevorzugt den Download-Token.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 12:12:24 +02:00

46 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Baut eine Download-URL für ein im Backend gespeichertes Upload-File.
*
* Geht über `GET /api/files/download?path=...` der Backend-Controller
* macht einen Per-File-Ownership-Check (Pfad → Resource → canAccessCustomer
* / canAccessContract). Damit kann auch ein eingeloggter User keine
* fremden Dateien abrufen, selbst wenn er den Pfad kennen würde.
*
* <a href> und window.open senden keinen Authorization-Header, daher
* Token als Query-Parameter (auth-Middleware akzeptiert `?token=<jwt>`).
*
* Trade-off: Tokens in URLs können in Logs/Referrer landen. Eine
* sauberere Lösung mit kurzlebigen Download-Tokens (signierte URLs)
* wäre v1.1-Item.
*/
import { getAccessToken } from '../services/api';
/**
* Kurzform für Inline-Vorschau: identisch zu `fileUrl(path, { inline: true })`.
* Verwenden für „Anzeigen"-Links / target="_blank"-Vorschauen. Default-
* `fileUrl(path)` bleibt für Downloads (Content-Disposition: attachment).
*/
export function viewUrl(path: string | null | undefined): string {
return fileUrl(path, { inline: true });
}
export function fileUrl(
path: string | null | undefined,
opts?: { inline?: boolean; token?: string },
): string {
if (!path) return '';
// Expliziter Token (z.B. kurzlebiger 60s-Download-Token) hat Vorrang vor
// dem langlebigen Access-Token. Genutzt vom PdfDragButton, damit bei einem
// Fehl-Drop kein 15-Min-Vollzugriffs-Token in fremde Kontexte leakt.
const token = opts?.token ?? getAccessToken();
const normalizedPath = path.startsWith('/') ? path : '/' + path;
// `?disposition=inline` schaltet die Anzeige im Browser-Tab ein,
// der Backend-Controller bleibt aber nur dann inline, wenn die
// Datei tatsächlich ein safe Type (PDF/PNG/JPEG/GIF/WebP) ist
// sonst fällt's auf attachment zurück. Default attachment.
const dispParam = opts?.inline ? '&disposition=inline' : '';
const base = `/api/files/download?path=${encodeURIComponent(normalizedPath)}${dispParam}`;
if (!token) return base;
return `${base}&token=${encodeURIComponent(token)}`;
}