@secondlayer/sdk 6.24.1 → 6.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1741,10 +1741,21 @@ type CreateStreamsClientOptions = {
1741
1741
  */
1742
1742
  dumpsBaseUrl?: string
1743
1743
  /**
1744
- * Verify the ed25519 `X-Signature` on every response (default off). Pass
1745
- * `true` to fetch the server's public key from
1746
- * `/public/streams/signing-key`, or `{ publicKey }` to pin a known PEM. A
1747
- * failed or missing signature throws `StreamsSignatureError`.
1744
+ * Verify the ed25519 `X-Signature` on every REST response and per-frame SSE
1745
+ * signature. Three states:
1746
+ * - **default (omitted)** *lenient*: verify when the server signs (the
1747
+ * hosted API signs every response), and pass through when no signature is
1748
+ * present (e.g. a self-hosted instance with no `STREAMS_SIGNING_PRIVATE_KEY`).
1749
+ * So verification is on by default against the hosted API without breaking
1750
+ * unsigned self-host deployments. An *invalid* signature always throws.
1751
+ * - **`true`** (or `{ publicKey }` to pin a known PEM) — *strict*: a missing
1752
+ * OR invalid signature throws `StreamsSignatureError`. Use this when you
1753
+ * require a portable, non-repudiable attestation and won't accept unsigned
1754
+ * data (it also closes the lenient mode's strip-the-header downgrade).
1755
+ * - **`false`** — off.
1756
+ *
1757
+ * The key is fetched once from `/public/streams/signing-key` (cached; a
1758
+ * rotated `X-Signature-KeyId` triggers one refresh) unless a PEM is pinned.
1748
1759
  */
1749
1760
  verify?: boolean | {
1750
1761
  publicKey: string
@@ -2293,8 +2304,13 @@ type WebhookHeaderInput = HeaderLookup | StandardWebhooksHeaders | Record<string
2293
2304
  *
2294
2305
  * The signed content is `${id}.${timestamp}.${rawBody}` HMAC-SHA256 with the
2295
2306
  * signing secret. Secrets returned by `sl subscriptions create` (or
2296
- * `rotate-secret`) carry a `whsec_` prefix and are base64-decoded after the
2297
- * prefix is stripped, matching the Svix / Standard Webhooks convention.
2307
+ * `rotate-secret`) are a bare 64-character hex string used directly as the
2308
+ * HMAC key (its UTF-8 bytes) this helper handles that. A `whsec_`-prefixed
2309
+ * base64 secret (the Svix convention) is also accepted and base64-decoded after
2310
+ * the prefix is stripped. Note: because the issued secret is bare hex (no
2311
+ * `whsec_` prefix), a generic Svix / Standard Webhooks library will base64-
2312
+ * decode it and derive the wrong key — verify with this helper (or with
2313
+ * {@link verifySecondlayerSignature}, the format-agnostic ed25519 path).
2298
2314
  *
2299
2315
  * @param rawBody The raw request body as a string. NEVER pass
2300
2316
  * `JSON.stringify(req.body)` — re-stringifying drops
@@ -2308,9 +2324,10 @@ type WebhookHeaderInput = HeaderLookup | StandardWebhooksHeaders | Record<string
2308
2324
  * a header value by name. Header name matching is
2309
2325
  * case-insensitive.
2310
2326
  * @param secret The signing secret returned by
2311
- * `sl subscriptions create` / `rotateSecret`. Pass it
2312
- * through verbatim — the helper handles the `whsec_`
2313
- * prefix.
2327
+ * `sl subscriptions create` / `rotateSecret` (a bare
2328
+ * 64-char hex string). Pass it through verbatim — the
2329
+ * helper accepts both bare hex and `whsec_`-prefixed
2330
+ * base64 secrets.
2314
2331
  * @param toleranceSeconds Max age of `webhook-timestamp` in seconds. Default
2315
2332
  * 300 (5 min) per the Standard Webhooks spec.
2316
2333
  * @returns true if every header is present, the timestamp is within
package/dist/index.js CHANGED
@@ -1319,10 +1319,16 @@ function subscribeStreamsEvents(opts) {
1319
1319
  }
1320
1320
  if (!parsed.event)
1321
1321
  continue;
1322
- if (opts.verify) {
1323
- const key = await opts.loadKey();
1324
- if (!parsed.sig || !ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
1325
- throw new StreamsSignatureError("Streams SSE frame signature is missing or invalid.");
1322
+ if (opts.verify !== "off") {
1323
+ if (!parsed.sig) {
1324
+ if (opts.verify === "strict") {
1325
+ throw new StreamsSignatureError("Streams SSE frame signature is missing.");
1326
+ }
1327
+ } else {
1328
+ const key = await opts.loadKey();
1329
+ if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
1330
+ throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
1331
+ }
1326
1332
  }
1327
1333
  }
1328
1334
  cursor = parsed.event.cursor ?? cursor;
@@ -1454,7 +1460,8 @@ async function mapStreamsError(response) {
1454
1460
  function createStreamsClient(options) {
1455
1461
  const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_STREAMS_BASE_URL);
1456
1462
  const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
1457
- const verify = options.verify ?? false;
1463
+ const verify = options.verify;
1464
+ const verifyMode = verify === false ? "off" : verify === undefined ? "lenient" : "strict";
1458
1465
  let keyPromise = null;
1459
1466
  function loadKey() {
1460
1467
  if (keyPromise)
@@ -1496,25 +1503,28 @@ function createStreamsClient(options) {
1496
1503
  if (!response.ok)
1497
1504
  await mapStreamsError(response);
1498
1505
  const text = await response.text();
1499
- if (verify) {
1506
+ if (verifyMode !== "off") {
1500
1507
  const signature = response.headers.get("X-Signature");
1501
1508
  if (!signature) {
1502
- throw new StreamsSignatureError("Response is missing X-Signature.");
1503
- }
1504
- const responseKeyId = response.headers.get("X-Signature-KeyId");
1505
- let key = await loadKey();
1506
- if (responseKeyId && responseKeyId !== key.keyId) {
1507
- if (typeof verify === "object") {
1508
- throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
1509
+ if (verifyMode === "strict") {
1510
+ throw new StreamsSignatureError("Response is missing X-Signature.");
1509
1511
  }
1510
- keyPromise = null;
1511
- key = await loadKey();
1512
- if (responseKeyId !== key.keyId) {
1513
- throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
1512
+ } else {
1513
+ const responseKeyId = response.headers.get("X-Signature-KeyId");
1514
+ let key = await loadKey();
1515
+ if (responseKeyId && responseKeyId !== key.keyId) {
1516
+ if (typeof verify === "object") {
1517
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
1518
+ }
1519
+ keyPromise = null;
1520
+ key = await loadKey();
1521
+ if (responseKeyId !== key.keyId) {
1522
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
1523
+ }
1524
+ }
1525
+ if (!ed255192.verifyEd25519(text, signature, key.publicKey)) {
1526
+ throw new StreamsSignatureError;
1514
1527
  }
1515
- }
1516
- if (!ed255192.verifyEd25519(text, signature, key.publicKey)) {
1517
- throw new StreamsSignatureError;
1518
1528
  }
1519
1529
  }
1520
1530
  return JSON.parse(text);
@@ -1618,7 +1628,7 @@ function createStreamsClient(options) {
1618
1628
  baseUrl,
1619
1629
  apiKey: options.apiKey,
1620
1630
  fetchImpl,
1621
- verify: Boolean(verify),
1631
+ verify: verifyMode,
1622
1632
  loadKey,
1623
1633
  params
1624
1634
  });
@@ -2505,5 +2515,5 @@ export {
2505
2515
  ApiError
2506
2516
  };
2507
2517
 
2508
- //# debugId=83F19CEB4B72C83564756E2164756E21
2518
+ //# debugId=9B0B41D8F5C2671064756E2164756E21
2509
2519
  //# sourceMappingURL=index.js.map