diff --git a/backend/src/plugins/auth.ts b/backend/src/plugins/auth.ts index 2b442a8..ca5532c 100644 --- a/backend/src/plugins/auth.ts +++ b/backend/src/plugins/auth.ts @@ -68,6 +68,15 @@ export default fp(async (server: FastifyInstance) => { return } + const urlPath = req.url.split("?")[0] + const queryToken = (req.query as any)?.downloadToken + const downloadToken = + typeof queryToken === "string" + && urlPath.startsWith("/api/email/attachments/") + && urlPath.endsWith("/download") + ? queryToken + : null + // 1️⃣ Token aus Header oder Cookie lesen const cookieToken = req.cookies?.token const authHeader = req.headers.authorization @@ -78,7 +87,7 @@ export default fp(async (server: FastifyInstance) => { const token = headerToken && headerToken.length > 10 ? headerToken - : cookieToken || null + : cookieToken || downloadToken || null if (!token) { return reply.code(401).send({ error: "Authentication required" }) diff --git a/frontend/pages/email/index.vue b/frontend/pages/email/index.vue index 7d8c1d9..0b42c2c 100644 --- a/frontend/pages/email/index.vue +++ b/frontend/pages/email/index.vue @@ -55,6 +55,7 @@ type EmailMessage = { } const { $api } = useNuxtApp() +const runtimeConfig = useRuntimeConfig() const toast = useToast() const accounts = ref([]) @@ -456,24 +457,22 @@ async function moveSelectedMessage() { async function downloadAttachment(attachment: NonNullable[number]) { actionLoading.value = `attachment-${attachment.id}` try { - const response = await $api.raw(`/api/email/attachments/${attachment.id}/download`, { - responseType: "arrayBuffer", - timeout: 60_000, - }) - const contentType = response.headers.get("content-type") || attachment.contentType || "application/octet-stream" - const blob = new Blob([response._data as ArrayBuffer], { type: contentType }) - const disposition = response.headers.get("content-disposition") || "" - const dispositionFilename = disposition.match(/filename="([^"]+)"/)?.[1] - const filename = dispositionFilename || attachment.filename || "anhang" - const url = URL.createObjectURL(blob) + const apiBase = String(runtimeConfig.public.apiBase || "").replace(/\/$/, "") + const path = `/api/email/attachments/${attachment.id}/download` + const downloadUrl = new URL(apiBase ? `${apiBase}${path}` : path, window.location.origin) + const token = useCookie("token").value + + if (token) { + downloadUrl.searchParams.set("downloadToken", token) + } + const link = document.createElement("a") - link.href = url - link.download = filename + link.href = downloadUrl.toString() + link.download = attachment.filename || "anhang" document.body.appendChild(link) link.click() link.remove() - URL.revokeObjectURL(url) } catch (err: any) { toast.add({ title: "Download fehlgeschlagen", @@ -481,7 +480,9 @@ async function downloadAttachment(attachment: NonNullable { + actionLoading.value = "" + }, 750) } }