133 lines
4.5 KiB
JavaScript
Executable File
133 lines
4.5 KiB
JavaScript
Executable File
import fs from 'fs/promises'
|
|
import path from 'path'
|
|
import { createHmac, timingSafeEqual } from 'crypto'
|
|
import { getUserFromToken } from '../../../utils/auth.js'
|
|
import { getServerDataPath } from '../../../utils/paths.js'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
// Datei-ID aus der URL extrahieren
|
|
const fileId = decodeURIComponent(getRouterParam(event, 'id'))
|
|
if (!fileId) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Datei-ID fehlt'
|
|
})
|
|
}
|
|
|
|
// Upload-Verzeichnis finden (intern)
|
|
const uploadDir = getServerDataPath('uploads')
|
|
console.log('Upload-Verzeichnis:', uploadDir)
|
|
|
|
// Alle Dateien im Upload-Verzeichnis durchsuchen
|
|
const files = await fs.readdir(uploadDir)
|
|
console.log('Verfügbare Dateien:', files)
|
|
console.log('Gesuchte Datei-ID:', fileId)
|
|
|
|
const matchingFile = files.find(file => file.includes(fileId))
|
|
console.log('Gefundene Datei:', matchingFile)
|
|
|
|
if (!matchingFile) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: 'Datei nicht gefunden'
|
|
})
|
|
}
|
|
|
|
// Prüfen ob der Benutzer berechtigt ist, diese Datei herunterzuladen
|
|
const token = getCookie(event, 'auth_token')
|
|
let isAuthorized = false
|
|
|
|
if (token) {
|
|
// Authentifizierte Benutzer prüfen
|
|
const user = await getUserFromToken(token)
|
|
const roles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : [])
|
|
if (user && (roles.includes('admin') || roles.includes('vorstand'))) {
|
|
// Admin/Vorstand kann alle Dateien herunterladen
|
|
isAuthorized = true
|
|
}
|
|
}
|
|
|
|
// Native apps cannot reliably reuse the httpOnly browser cookie that is
|
|
// set when the application is created. They receive the same short-lived
|
|
// authorization as a signed response token instead.
|
|
const signedDownloadToken = getHeader(event, 'x-membership-download-token')
|
|
if (signedDownloadToken) {
|
|
try {
|
|
const [payload, signature] = signedDownloadToken.split('.')
|
|
const secret = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
|
|
const expected = createHmac('sha256', secret).update(payload).digest('base64url')
|
|
const validSignature = signature && timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
|
|
const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'))
|
|
const tokenAge = Date.now() - Number(decoded.issuedAt)
|
|
if (validSignature && decoded.fileId === fileId && tokenAge >= 0 && tokenAge < 24 * 60 * 60 * 1000) {
|
|
isAuthorized = true
|
|
}
|
|
} catch (_error) {
|
|
// Invalid download tokens are treated as unauthorized.
|
|
}
|
|
}
|
|
|
|
// Browser clients continue to use the httpOnly cookie.
|
|
const downloadToken = getCookie(event, 'download_token')
|
|
|
|
if (downloadToken) {
|
|
try {
|
|
const decoded = Buffer.from(downloadToken, 'base64').toString('utf8')
|
|
const [tokenFilename, timestamp] = decoded.split(':')
|
|
|
|
// Prüfen ob der Token für diese Datei ist und nicht älter als 24 Stunden
|
|
if (tokenFilename === fileId.replace('.pdf', '') &&
|
|
Date.now() - parseInt(timestamp) < 24 * 60 * 60 * 1000) {
|
|
isAuthorized = true
|
|
}
|
|
} catch (e) {
|
|
console.warn('Ungültiger Download-Token:', e.message)
|
|
}
|
|
}
|
|
|
|
if (!isAuthorized) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
statusMessage: 'Keine Berechtigung für diesen Download'
|
|
})
|
|
}
|
|
|
|
const filePath = path.join(uploadDir, matchingFile)
|
|
|
|
// Datei lesen
|
|
const fileBuffer = await fs.readFile(filePath)
|
|
|
|
// MIME-Type basierend auf Dateiendung bestimmen
|
|
const ext = path.extname(matchingFile).toLowerCase()
|
|
let mimeType = 'application/octet-stream'
|
|
let filename = matchingFile
|
|
|
|
if (ext === '.pdf') {
|
|
mimeType = 'application/pdf'
|
|
} else if (ext === '.txt') {
|
|
mimeType = 'text/plain'
|
|
filename = matchingFile.replace('.txt', '.pdf') // Für Download als PDF benennen
|
|
}
|
|
|
|
// Datei als Download senden
|
|
setHeader(event, 'Content-Type', mimeType)
|
|
setHeader(event, 'Content-Disposition', `attachment; filename="${filename}"`)
|
|
setHeader(event, 'Content-Length', fileBuffer.length.toString())
|
|
|
|
return fileBuffer
|
|
|
|
} catch (error) {
|
|
console.error('Download-Fehler:', error)
|
|
|
|
if (error.statusCode) {
|
|
throw error
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Interner Serverfehler'
|
|
})
|
|
}
|
|
})
|