Aufträge L5: Bestätigungs-Token (32 Zufallsbytes, nur als Hash gespeichert, 60 Tage gültig)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8627ed1124
commit
cf3e0fd52a
2 files changed
+58
No files matched your search
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Bestätigungs-Token einer Auftragsbestätigung (Übersicht V4 „L5“): 32 Zufallsbytes als base64url (43 Zeichen),
|
||||
* gespeichert wird nur der SHA-256-Hash in Hex. Gültig 60 Tage ab dem letzten Versand.
|
||||
* Importiert bewusst nur node:crypto und konstanten.js (nie server/config.js).
|
||||
*/
|
||||
import { createHash, randomBytes } from 'node:crypto'
|
||||
import { TOKEN_GUELTIG_TAGE } from './konstanten.js'
|
||||
|
||||
const TAG_MS = 86_400_000
|
||||
const TOKEN_FORMAT = /^[A-Za-z0-9_-]{43}$/
|
||||
|
||||
const sha256Hex = (text) => createHash('sha256').update(text).digest('hex')
|
||||
|
||||
export function neuerToken(jetzt = new Date()) {
|
||||
if (!(jetzt instanceof Date) || !Number.isFinite(jetzt.getTime())) throw new TypeError('neuerToken: jetzt muss ein gültiges Date sein')
|
||||
const token = randomBytes(32).toString('base64url')
|
||||
return { token, hash: sha256Hex(token), gueltigBis: new Date(jetzt.getTime() + TOKEN_GUELTIG_TAGE * TAG_MS) }
|
||||
}
|
||||
|
||||
/** Hash eines Tokens aus einem Link; `null`, wenn der Text nicht das Format eines Tokens hat. */
|
||||
export function hashToken(token) {
|
||||
return typeof token === 'string' && TOKEN_FORMAT.test(token) ? sha256Hex(token) : null
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { hashToken, neuerToken } from '../server/services/auftraege/token.js'
|
||||
|
||||
const TAG_MS = 24 * 60 * 60 * 1000
|
||||
|
||||
test('neuerToken: 32 Zufallsbytes als base64url (43 Zeichen), Hash = SHA-256 in Hex, gültig 60 Tage ab jetzt', () => {
|
||||
const jetzt = new Date('2026-10-06T10:00:00.000Z')
|
||||
const { token, hash, gueltigBis } = neuerToken(jetzt)
|
||||
assert.match(token, /^[A-Za-z0-9_-]{43}$/)
|
||||
assert.equal(hash, createHash('sha256').update(token).digest('hex'))
|
||||
assert.ok(gueltigBis instanceof Date)
|
||||
assert.equal(gueltigBis.toISOString(), '2026-12-05T10:00:00.000Z')
|
||||
assert.equal(jetzt.toISOString(), '2026-10-06T10:00:00.000Z', 'das übergebene Datum bleibt unverändert')
|
||||
})
|
||||
|
||||
test('neuerToken: jedes Token ist neu; ohne Argument zählt die aktuelle Zeit; ein ungültiger Zeitpunkt wirft', () => {
|
||||
const vorher = Date.now()
|
||||
const a = neuerToken()
|
||||
const b = neuerToken()
|
||||
const nachher = Date.now()
|
||||
assert.notEqual(a.token, b.token)
|
||||
assert.notEqual(a.hash, b.hash)
|
||||
assert.ok(a.gueltigBis.getTime() >= vorher + 60 * TAG_MS && a.gueltigBis.getTime() <= nachher + 60 * TAG_MS)
|
||||
assert.throws(() => neuerToken(new Date('kein Datum')), TypeError)
|
||||
assert.throws(() => neuerToken('2026-10-06'), TypeError)
|
||||
})
|
||||
|
||||
test('hashToken: gleicher Hash wie neuerToken, null bei falschem Format', () => {
|
||||
const { token, hash } = neuerToken()
|
||||
assert.equal(hashToken(token), hash)
|
||||
const falsch = [undefined, null, 42, {}, [], '', 'kurz', `${token}x`, token.slice(1), ` ${token.slice(0, 42)}`, `${token.slice(0, 42)}+`, `${token.slice(0, 42)}=`, `../${token.slice(3)}`, `${token.slice(0, 42)}\n`]
|
||||
for (const wert of falsch) assert.equal(hashToken(wert), null, JSON.stringify(wert))
|
||||
})
|
||||
Reference in new issue
Block a user