Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
63 lines
2.4 KiB
TypeScript
63 lines
2.4 KiB
TypeScript
import { createHmac, timingSafeEqual } from 'node:crypto'
|
|
import { getEnv } from '@/lib/env'
|
|
|
|
/**
|
|
* Kurzlebiges Zeichen für den Druckabruf.
|
|
*
|
|
* Der PDF-Dienst ruft die Druckansicht selbst auf und braucht dafür die Rechte
|
|
* des Abrufers. Ihm dafür das **Sitzungscookie** mitzugeben wäre der bequeme
|
|
* Weg und der falsche: ein Cookie ist der volle Zugang zum Konto, und es läge
|
|
* dann im Speicher eines zweiten Prozesses und in dessen Protokollen.
|
|
*
|
|
* Stattdessen ein signiertes Zeichen, das genau eines erlaubt: diese eine
|
|
* Ansicht, für diesen einen Auftrag, für ein paar Minuten. Es taugt zu nichts
|
|
* anderem. Vergleichbar mit der signierten Datei-URL aus docs/07.
|
|
*/
|
|
|
|
/** Lange genug für einen langsamen Rendervorgang, kurz genug zum Wegwerfen. */
|
|
const GUELTIG_MS = 10 * 60_000
|
|
|
|
export type RenderClaim = {
|
|
jobId: string
|
|
userId: string
|
|
clearanceLevel: number
|
|
expiresAt: number
|
|
}
|
|
|
|
function signieren(nutzlast: string): string {
|
|
return createHmac('sha256', getEnv().AUTH_SECRET).update(nutzlast).digest('base64url')
|
|
}
|
|
|
|
export function createRenderToken(claim: Omit<RenderClaim, 'expiresAt'>): string {
|
|
const vollstaendig: RenderClaim = { ...claim, expiresAt: Date.now() + GUELTIG_MS }
|
|
const nutzlast = Buffer.from(JSON.stringify(vollstaendig)).toString('base64url')
|
|
return `${nutzlast}.${signieren(nutzlast)}`
|
|
}
|
|
|
|
/**
|
|
* Prüft das Zeichen. Gibt `null` zurück, wenn irgendetwas nicht stimmt - der
|
|
* Aufrufer macht daraus 404, nicht 403: dass es die Druckansicht gibt, geht
|
|
* niemanden etwas an, der kein gültiges Zeichen hat.
|
|
*/
|
|
export function verifyRenderToken(token: string | null | undefined): RenderClaim | null {
|
|
if (!token) return null
|
|
|
|
const [nutzlast, unterschrift] = token.split('.')
|
|
if (!nutzlast || !unterschrift) return null
|
|
|
|
const erwartet = signieren(nutzlast)
|
|
// Zeitkonstanter Vergleich: ein Längenvergleich vorweg, weil timingSafeEqual
|
|
// bei ungleicher Länge wirft.
|
|
if (erwartet.length !== unterschrift.length) return null
|
|
if (!timingSafeEqual(Buffer.from(erwartet), Buffer.from(unterschrift))) return null
|
|
|
|
try {
|
|
const claim = JSON.parse(Buffer.from(nutzlast, 'base64url').toString('utf8')) as RenderClaim
|
|
if (typeof claim.expiresAt !== 'number' || claim.expiresAt < Date.now()) return null
|
|
if (typeof claim.userId !== 'string' || typeof claim.jobId !== 'string') return null
|
|
return claim
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|