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>
46 lines
2.0 KiB
TypeScript
46 lines
2.0 KiB
TypeScript
/**
|
||
* 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)}`;
|
||
}
|