28 lines
1.4 KiB
JavaScript
28 lines
1.4 KiB
JavaScript
import { createHmac, timingSafeEqual } from 'node:crypto'
|
|
import { config } from '../config.js'
|
|
|
|
/**
|
|
* Signierte Datei-Links fuer das Ticketsystem (Spec 2026-10-01, Abschnitt 4 "Dateien ohne Header"): <img>, <audio>
|
|
* und Downloads koennen keinen Authorization-Header tragen. Der Link gilt 15 Minuten und nur fuer genau diese
|
|
* Datei dieses Kunden; signiert mit dem Sitzungs-Geheimnis des Portals.
|
|
*/
|
|
export const FILE_LINK_TTL_MS = 15 * 60 * 1000
|
|
|
|
const digest = (kind, customerId, id, exp) =>
|
|
createHmac('sha256', config.sessionSecret).update(`${kind}:${customerId}:${id}:${exp}`).digest('hex')
|
|
|
|
export function signFileLink({ kind, customerId, id, now = Date.now() }) {
|
|
const exp = now + FILE_LINK_TTL_MS
|
|
return { exp, sig: digest(kind, customerId, id, exp) }
|
|
}
|
|
|
|
/** exp kommt aus der Adresse (Text oder Zahl); falsch bei Ablauf, fremder Datei oder kaputter Signatur. */
|
|
export function verifyFileLink({ kind, customerId, id, exp, sig, now = Date.now() }) {
|
|
const expiry = typeof exp === 'number' ? exp : /^\d+$/.test(String(exp ?? '')) ? Number(exp) : NaN
|
|
if (!Number.isFinite(expiry) || now >= expiry) return false
|
|
if (typeof sig !== 'string' || !/^[0-9a-f]{64}$/.test(sig)) return false
|
|
const expected = Buffer.from(digest(kind, customerId, id, expiry), 'hex')
|
|
const given = Buffer.from(sig, 'hex')
|
|
return given.length === expected.length && timingSafeEqual(given, expected)
|
|
}
|