Gutschriften: signierte Links fuer Nachweise im Ticketsystem

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-02 02:51:49 +00:00
1 parent 7d93ae0cd9
commit 0fdd13246c
3 files changed
+66 -2

No files matched your search

+14 -1
View File
@@ -2,6 +2,7 @@ import { Router } from 'express'
import { config } from '../config.js'
import { getSessionCustomerId, requireSession } from '../middleware/session.js'
import { getDocument, listDocuments, Query } from '../services/appwriteAdmin.js'
import { signFileLink } from '../services/signedLinks.js'
import {
answerQuestion,
applyTakeoverCredit,
@@ -176,9 +177,21 @@ takeoverAdminRouter.get('/open-count', async (_req, res) => {
}
})
// Nachweise bekommen einen signierten Link (15 min) auf den Team-Eingang: das Ticketsystem oeffnet sie ohne
// Authorization-Header (routes/team/files.js). Bei jedem Oeffnen des Antrags neu signiert.
function withSignedFileUrls(detail, now = Date.now()) {
const id = detail.request.id
const base = `${config.publicBaseUrl}/api/team/takeover-credits/${encodeURIComponent(id)}/files`
const files = detail.request.files.map((file) => {
const { exp, sig } = signFileLink({ kind: 'takeover', customerId: id, id: file.id, now })
return { ...file, url: `${base}/${encodeURIComponent(file.id)}?exp=${exp}&sig=${sig}` }
})
return { ...detail, request: { ...detail.request, files } }
}
takeoverAdminRouter.get('/:id', async (req, res) => {
try {
return res.json(await getAdminDetail(req.params.id))
return res.json(withSignedFileUrls(await getAdminDetail(req.params.id)))
} catch (err) {
return sendError(res, err, 'Antrag konnte nicht geladen werden')
}
+21 -1
View File
@@ -1,9 +1,10 @@
import { Router } from 'express'
import { openAttachment, sendAttachment } from '../../services/chatAttachments.js'
import { verifyFileLink } from '../../services/signedLinks.js'
import { getTakeoverRequest, readUpload } from '../../services/takeoverCredits.js'
/**
* Anhaenge mit signiertem Link (?exp=&sig=, 15 min) ohne JWT ausliefern: <img>, <audio> und Downloads im
* Chat-Anhaenge und Gutschrift-Nachweise mit signiertem Link (?exp=&sig=, 15 min) ohne JWT ausliefern: <img>, <audio> und Downloads im
* Ticketsystem koennen keinen Authorization-Header tragen. Ohne sig geht es weiter zur JWT-Pruefung.
* Eingehaengt in routes/team/index.js VOR requireStaffJwt.
*/
@@ -23,4 +24,23 @@ router.get('/chats/:customerId/attachments/:id', async (req, res, next) => {
}
})
// Nachweise einer Uebernahmegutschrift (wie takeoverAdminRouter GET /:id/files/:fileId), signiert ueber die Antrags-ID
router.get('/takeover-credits/:id/files/:fileId', async (req, res, next) => {
if (req.query.sig === undefined) return next()
const { id, fileId } = req.params
if (!verifyFileLink({ kind: 'takeover', customerId: id, id: fileId, exp: req.query.exp, sig: String(req.query.sig) })) {
return res.status(403).json({ error: 'Link abgelaufen. Bitte den Antrag neu öffnen.' })
}
try {
const { file, content } = await readUpload(await getTakeoverRequest(id), fileId)
res.setHeader('Content-Type', file.type)
res.setHeader('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(file.name)}`)
res.setHeader('X-Content-Type-Options', 'nosniff')
res.setHeader('Cache-Control', 'private, no-store')
return res.send(content)
} catch (err) {
return res.status(err.status || 500).send(err.message || 'Datei nicht verfügbar')
}
})
export default router