/** * Pruefung der Signatur eingehender Kasseneck-Webhooks. * * **Das ist der Teil, den Integratoren am haeufigsten falsch bauen** — und der * einzige, bei dem ein Fehler nicht auffaellt: eine zu lasche Pruefung laesst * jeden durch, der die Adresse kennt, und meldet dabei nie etwas. Deshalb * liegt sie fertig im Paket und nicht als Beispielschnipsel in der Doku. * * Vier Dinge muessen stimmen, und jedes einzelne fehlt in der Praxis regelmaessig: * * 1. **Der ROHE Rumpf.** Signiert werden die Bytes, die ankommen — nicht das * Ergebnis von `JSON.parse` und erneutem `JSON.stringify`. Schluesselreihen- * folge, Zahlenschreibweise und Leerraum aendern sich dabei, und die * Signatur passt nicht mehr. In Express heisst das `express.raw({type:'*​/*'})` * **vor** jedem JSON-Parser fuer diesen Pfad. * 2. **Das Zeitfenster.** Ohne Pruefung von `t` ist eine einmal mitgeschnittene, * gueltig signierte Zustellung fuer immer wiederverwendbar. 300 Sekunden in * beide Richtungen — auch in die Zukunft, sonst hilft eine falsch gestellte * Uhr auf der Gegenseite dem Angreifer. * 3. **Der zeitkonstante Vergleich.** Ein `===` auf Hex-Zeichenketten bricht * beim ersten abweichenden Zeichen ab. Wer messen kann, wie lange die * Ablehnung dauert, raet die Signatur Zeichen fuer Zeichen. * 4. **Jede Ausnahme ist eine Ablehnung.** Ein `catch`, das weiterlaufen laesst, * macht aus einem Formfehler ein Ja. * * Der Kopf lautet `X-Kasseneck-Signature: t=,v1=` mit * `v1 = HMAC-SHA256(secret, ".")`. **Mehrere `v1=`-Anteile sind * erlaubt** — so laeuft ein Schluesselwechsel ohne Zustellungsluecke; es reicht, * wenn einer passt. * * Umgesetzt mit WebCrypto (`crypto.subtle`) statt `node:crypto`: dieses Paket * haengt an keiner Laufzeit-Abhaengigkeit und wird auch fuer den Browser * gebuendelt. Deshalb ist die Pruefung **asynchron**. */ /** Der Kopf, in dem die Signatur steht. */ export declare const WEBHOOK_SIGNATURE_HEADER = "X-Kasseneck-Signature"; /** Der Kopf mit dem Ereignisnamen (dasselbe wie `body.type`). */ export declare const WEBHOOK_EVENT_HEADER = "X-Kasseneck-Event"; /** Der Kopf mit der Zustell-Kennung — bei Wiederholungen **dieselbe**. */ export declare const WEBHOOK_DELIVERY_HEADER = "X-Kasseneck-Delivery"; /** Erlaubte Abweichung des Zeitstempels, in Sekunden (in beide Richtungen). */ export declare const WEBHOOK_TOLERANCE_SEC = 300; /** Wartezeiten zwischen den Zustellversuchen, in Sekunden. */ export declare const WEBHOOK_RETRY_PLAN_SEC: readonly number[]; /** Erstversuch plus je eine Wiederholung pro Planeintrag. */ export declare const WEBHOOK_MAX_ATTEMPTS: number; /** So lange wartet Kasseneck auf eine 2xx-Antwort. */ export declare const WEBHOOK_TIMEOUT_MS = 10000; /** Hoechstzahl der Webhook-Endpunkte je Partner. */ export declare const WEBHOOK_LIMIT = 10; /** Warum eine Zustellung abgelehnt wurde. Jeder Grund ist ein Nein. */ export type WebhookVerifyReason = 'secret-missing' | 'header-missing' | 'header-malformed' | 'body-missing' | 'timestamp-outside-window' | 'signature-mismatch'; export type WebhookVerifyResult = { ok: true; timestampSec: number; } | { ok: false; reason: WebhookVerifyReason; }; export interface VerifyWebhookOptions { /** * Das Secret aus `createPartnerWebhook` — es verlaesst den Server genau * einmal. Eine Liste erlaubt den Schluesselwechsel: es reicht, wenn einer * passt. */ secret: string | readonly string[]; /** Der Wert des Kopfes `X-Kasseneck-Signature`, unveraendert. */ signatureHeader: string | null | undefined; /** * Der **rohe** Rumpf — Bytes oder die daraus gelesene Zeichenkette. Nicht * das Ergebnis von `JSON.parse`, und nichts, was danach wieder * zusammengesetzt wurde. */ body: string | Uint8Array; /** Jetzt, in Unix-Sekunden. Vorgabe: die Systemuhr. Fuer Tests setzbar. */ nowSec?: number; /** Abweichender Toleranzrahmen in Sekunden; Vorgabe [WEBHOOK_TOLERANCE_SEC]. */ toleranceSec?: number; } /** * Prueft die Signatur einer eingehenden Zustellung. * * Antwortet **nie** mit einem geworfenen Fehler auf schlechte Eingaben: jede * Ausnahme im Inneren wird zur Ablehnung. Ein Aufrufer, der nur `ok` abfragt, * kann damit nichts falsch machen. */ export declare function verifyWebhookSignature(options: VerifyWebhookOptions): Promise; /** * Zerlegt den Signaturkopf. `null`, wenn kein brauchbarer Zeitstempel oder gar * kein `v1=`-Anteil darin steht. */ export declare function parseSignatureHeader(header: string): { t: number; v1: string[]; } | null;