Push-Zeitmail: signierter Token für Zeit-Links
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cdc814ae09
commit
5b7365b99e
3 files changed
+96
No files matched your search
@@ -0,0 +1,40 @@
|
||||
import { createHmac, timingSafeEqual } from 'node:crypto'
|
||||
import { config } from '../config.js'
|
||||
|
||||
/**
|
||||
* Signierte Links fuer die Push-Zeitmail: <nutzlast>.<signatur>, 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 }
|
||||
}
|
||||
Reference in new issue
Block a user