import { createHmac, timingSafeEqual } from 'node:crypto' import { config } from '../config.js' /** * Signierte Links fuer die Push-Zeitmail: ., beides base64url. * Nutzlast { w: Arbeitsblatt-ID, e: Mitarbeiter-Dokument-ID, x: Ablauf in Unix-Sekunden }. * Zustandslos, damit Links einen Neustart ueberleben; der Link darf nur die Zeit genau dieses * Arbeitsblatts eintragen (siehe routes/pushTime.js). */ const sign = (payload) => createHmac('sha256', config.pushTime.secret).update(payload).digest('base64url') export function createPushTimeToken({ worksheetId, employeeId }, now = Date.now()) { if (!config.pushTime.secret) throw new Error('PUSH_TIME_SECRET fehlt') const expires = Math.floor(now / 1000) + config.pushTime.ttlDays * 86400 const payload = Buffer.from(JSON.stringify({ w: worksheetId, e: employeeId, x: expires })).toString('base64url') return `${payload}.${sign(payload)}` } export function verifyPushTimeToken(token, now = Date.now()) { const invalid = { error: 'invalid' } if (!config.pushTime.secret || typeof token !== 'string') return invalid const parts = token.split('.') if (parts.length !== 2 || !parts[0] || !parts[1]) return invalid const [payload, signature] = parts const expected = Buffer.from(sign(payload)) const given = Buffer.from(signature) if (given.length !== expected.length || !timingSafeEqual(given, expected)) return invalid let data try { data = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) } catch { return invalid } if (typeof data?.w !== 'string' || typeof data?.e !== 'string' || !Number.isFinite(data?.x)) return invalid if (data.x * 1000 <= now) return { error: 'expired' } return { worksheetId: data.w, employeeId: data.e } }