@cmssy/core 5.1.0 → 6.0.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.cjs CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  var jose = require('jose');
4
4
  var types = require('@cmssy/types');
5
- var crypto$1 = require('crypto');
6
5
 
7
6
  // src/session.ts
8
7
  var CMSSY_SESSION_COOKIE = "cmssy_session";
@@ -1639,6 +1638,8 @@ function formatPrice(minor, currency) {
1639
1638
  return `${amount.toFixed(fractionDigits(code))} ${code}`;
1640
1639
  }
1641
1640
  }
1641
+
1642
+ // src/verify-webhook.ts
1642
1643
  var CmssyWebhookError = class extends Error {
1643
1644
  constructor(message) {
1644
1645
  super(message);
@@ -1662,13 +1663,46 @@ function parseSignatureHeader(header) {
1662
1663
  }
1663
1664
  return { timestamp, signature };
1664
1665
  }
1666
+ function hexToBytes(hex) {
1667
+ if (hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex)) return null;
1668
+ const bytes = new Uint8Array(hex.length / 2);
1669
+ for (let i = 0; i < bytes.length; i += 1) {
1670
+ bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
1671
+ }
1672
+ return bytes;
1673
+ }
1674
+ function bytesToHex(bytes) {
1675
+ return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
1676
+ }
1665
1677
  function timingSafeHexEqual(expectedHex, providedHex) {
1666
- const expected = Buffer.from(expectedHex, "hex");
1667
- const provided = Buffer.from(providedHex, "hex");
1668
- if (expected.length !== provided.length) return false;
1669
- return crypto$1.timingSafeEqual(expected, provided);
1678
+ const expected = hexToBytes(expectedHex);
1679
+ const provided = hexToBytes(providedHex);
1680
+ if (!expected || !provided || expected.length !== provided.length) {
1681
+ return false;
1682
+ }
1683
+ let diff = 0;
1684
+ for (let i = 0; i < expected.length; i += 1) {
1685
+ diff |= (expected[i] ?? 0) ^ (provided[i] ?? 0);
1686
+ }
1687
+ return diff === 0;
1688
+ }
1689
+ async function hmacSha256Hex(secret, message) {
1690
+ const encoder = new TextEncoder();
1691
+ const key = await crypto.subtle.importKey(
1692
+ "raw",
1693
+ encoder.encode(secret),
1694
+ { name: "HMAC", hash: "SHA-256" },
1695
+ false,
1696
+ ["sign"]
1697
+ );
1698
+ const signature = await crypto.subtle.sign(
1699
+ "HMAC",
1700
+ key,
1701
+ encoder.encode(message)
1702
+ );
1703
+ return bytesToHex(new Uint8Array(signature));
1670
1704
  }
1671
- function verifyCmssyWebhook(options) {
1705
+ async function verifyCmssyWebhook(options) {
1672
1706
  const { body, signatureHeader, secret } = options;
1673
1707
  if (!signatureHeader) {
1674
1708
  throw new CmssyWebhookError("Missing X-Cmssy-Signature header");
@@ -1682,7 +1716,7 @@ function verifyCmssyWebhook(options) {
1682
1716
  if (Math.abs(now - timestamp) > toleranceMs) {
1683
1717
  throw new CmssyWebhookError("Webhook timestamp outside tolerance");
1684
1718
  }
1685
- const expected = crypto$1.createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
1719
+ const expected = await hmacSha256Hex(secret, `${timestamp}.${body}`);
1686
1720
  if (!timingSafeHexEqual(expected, signature)) {
1687
1721
  throw new CmssyWebhookError("Webhook signature mismatch");
1688
1722
  }
package/dist/index.d.cts CHANGED
@@ -491,6 +491,6 @@ declare class CmssyWebhookError extends Error {
491
491
  * `CmssyWebhookError` on any failure (missing/malformed header, bad
492
492
  * signature, stale timestamp, invalid JSON) - catch it and respond 400.
493
493
  */
494
- declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): CmssyWebhookEvent;
494
+ declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): Promise<CmssyWebhookEvent>;
495
495
 
496
496
  export { type AppToEditorMessage, type AuthMutationResult, type AuthTokenResult, type BoundsMessage, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CartRequestContext, type CheckoutInput, type ClickMessage, type CmssyClient, type CmssyConfig, type CmssyCspOptions, type CmssyEnvConfig, CmssyRequestError, CmssyWebhookError, DEFAULT_CMSSY_API_URL, DEFAULT_CMSSY_EDITOR_ORIGINS, type EditorToAppMessage, FORM_QUERY, type FetchLike, type FetchLikeResponse, type FetchPageOptions, type GraphqlRequestOptions, MIN_SESSION_SECRET_LENGTH, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PRODUCTS_QUERY, PROTOCOL_VERSION, type ParentReadyMessage, type PatchMessage, type PostTarget, type QueryScopedOptions, type ReadyMessage, type RetryPolicy, SESSION_MAX_AGE_SECONDS, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, type SelectMessage, applyCmssyCsp, asBucket, assertAuthConfig, backendAddToCart, backendApplyDiscount, backendCheckout, backendClearCart, backendForgotPassword, backendGetCart, backendMergeCart, backendMyOrder, backendMyOrders, backendOrderByToken, backendProduct, backendRefresh, backendRegister, backendRemoveDiscount, backendRemoveItem, backendResetPassword, backendSetShippingMethod, backendSignIn, backendSignOut, backendSignOutEverywhere, backendUpdateItem, backendVerifyEmail, buildBlockContext, buildLocaleSwitchHref, cachedWorkspaceId, clearWorkspaceIdCache, cmssyCspHeaders, cmssySecretsMatch, collectFormIds, createCmssyClient, decodeAccessClaims, defineCmssyConfig, fetchLayouts, fetchOrderByToken, fetchPage, fetchPageById, fetchPageMeta, fetchPages, fetchProduct, fetchProducts, fetchSiteConfig, fields, formatPrice, fractionDigits, fromMinorUnits, getBlockContentForLanguage, graphqlRequest, isAccessExpired, isDevelopment, isProtocolCompatible, isVerifiedEditUrl, localeForPath, localeForPathname, localizeHref, localizeHtmlLinks, localizedPath, normalizeOrigin, normalizeSlug, openSession, parseEditorMessage, postToEditor, resolveApiUrl, resolveEditorOrigin, resolveForms, resolveInitialTarget, resolvePublicUrl, resolveSeoLocales, resolveSiteLocales, resolveWorkspaceId, sealSession, sessionCookieOptions, splitCmssyLocale, splitLocaleFromPath, toCspOrigin, toMinorUnits, toSessionPayload, verifyCmssyWebhook };
package/dist/index.d.ts CHANGED
@@ -491,6 +491,6 @@ declare class CmssyWebhookError extends Error {
491
491
  * `CmssyWebhookError` on any failure (missing/malformed header, bad
492
492
  * signature, stale timestamp, invalid JSON) - catch it and respond 400.
493
493
  */
494
- declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): CmssyWebhookEvent;
494
+ declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): Promise<CmssyWebhookEvent>;
495
495
 
496
496
  export { type AppToEditorMessage, type AuthMutationResult, type AuthTokenResult, type BoundsMessage, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CartRequestContext, type CheckoutInput, type ClickMessage, type CmssyClient, type CmssyConfig, type CmssyCspOptions, type CmssyEnvConfig, CmssyRequestError, CmssyWebhookError, DEFAULT_CMSSY_API_URL, DEFAULT_CMSSY_EDITOR_ORIGINS, type EditorToAppMessage, FORM_QUERY, type FetchLike, type FetchLikeResponse, type FetchPageOptions, type GraphqlRequestOptions, MIN_SESSION_SECRET_LENGTH, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PRODUCTS_QUERY, PROTOCOL_VERSION, type ParentReadyMessage, type PatchMessage, type PostTarget, type QueryScopedOptions, type ReadyMessage, type RetryPolicy, SESSION_MAX_AGE_SECONDS, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, type SelectMessage, applyCmssyCsp, asBucket, assertAuthConfig, backendAddToCart, backendApplyDiscount, backendCheckout, backendClearCart, backendForgotPassword, backendGetCart, backendMergeCart, backendMyOrder, backendMyOrders, backendOrderByToken, backendProduct, backendRefresh, backendRegister, backendRemoveDiscount, backendRemoveItem, backendResetPassword, backendSetShippingMethod, backendSignIn, backendSignOut, backendSignOutEverywhere, backendUpdateItem, backendVerifyEmail, buildBlockContext, buildLocaleSwitchHref, cachedWorkspaceId, clearWorkspaceIdCache, cmssyCspHeaders, cmssySecretsMatch, collectFormIds, createCmssyClient, decodeAccessClaims, defineCmssyConfig, fetchLayouts, fetchOrderByToken, fetchPage, fetchPageById, fetchPageMeta, fetchPages, fetchProduct, fetchProducts, fetchSiteConfig, fields, formatPrice, fractionDigits, fromMinorUnits, getBlockContentForLanguage, graphqlRequest, isAccessExpired, isDevelopment, isProtocolCompatible, isVerifiedEditUrl, localeForPath, localeForPathname, localizeHref, localizeHtmlLinks, localizedPath, normalizeOrigin, normalizeSlug, openSession, parseEditorMessage, postToEditor, resolveApiUrl, resolveEditorOrigin, resolveForms, resolveInitialTarget, resolvePublicUrl, resolveSeoLocales, resolveSiteLocales, resolveWorkspaceId, sealSession, sessionCookieOptions, splitCmssyLocale, splitLocaleFromPath, toCspOrigin, toMinorUnits, toSessionPayload, verifyCmssyWebhook };
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { EncryptJWT, jwtDecrypt } from 'jose';
2
2
  export { evaluateFieldConditionGroup } from '@cmssy/types';
3
- import { createHmac, timingSafeEqual } from 'crypto';
4
3
 
5
4
  // src/session.ts
6
5
  var CMSSY_SESSION_COOKIE = "cmssy_session";
@@ -1637,6 +1636,8 @@ function formatPrice(minor, currency) {
1637
1636
  return `${amount.toFixed(fractionDigits(code))} ${code}`;
1638
1637
  }
1639
1638
  }
1639
+
1640
+ // src/verify-webhook.ts
1640
1641
  var CmssyWebhookError = class extends Error {
1641
1642
  constructor(message) {
1642
1643
  super(message);
@@ -1660,13 +1661,46 @@ function parseSignatureHeader(header) {
1660
1661
  }
1661
1662
  return { timestamp, signature };
1662
1663
  }
1664
+ function hexToBytes(hex) {
1665
+ if (hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex)) return null;
1666
+ const bytes = new Uint8Array(hex.length / 2);
1667
+ for (let i = 0; i < bytes.length; i += 1) {
1668
+ bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
1669
+ }
1670
+ return bytes;
1671
+ }
1672
+ function bytesToHex(bytes) {
1673
+ return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
1674
+ }
1663
1675
  function timingSafeHexEqual(expectedHex, providedHex) {
1664
- const expected = Buffer.from(expectedHex, "hex");
1665
- const provided = Buffer.from(providedHex, "hex");
1666
- if (expected.length !== provided.length) return false;
1667
- return timingSafeEqual(expected, provided);
1676
+ const expected = hexToBytes(expectedHex);
1677
+ const provided = hexToBytes(providedHex);
1678
+ if (!expected || !provided || expected.length !== provided.length) {
1679
+ return false;
1680
+ }
1681
+ let diff = 0;
1682
+ for (let i = 0; i < expected.length; i += 1) {
1683
+ diff |= (expected[i] ?? 0) ^ (provided[i] ?? 0);
1684
+ }
1685
+ return diff === 0;
1686
+ }
1687
+ async function hmacSha256Hex(secret, message) {
1688
+ const encoder = new TextEncoder();
1689
+ const key = await crypto.subtle.importKey(
1690
+ "raw",
1691
+ encoder.encode(secret),
1692
+ { name: "HMAC", hash: "SHA-256" },
1693
+ false,
1694
+ ["sign"]
1695
+ );
1696
+ const signature = await crypto.subtle.sign(
1697
+ "HMAC",
1698
+ key,
1699
+ encoder.encode(message)
1700
+ );
1701
+ return bytesToHex(new Uint8Array(signature));
1668
1702
  }
1669
- function verifyCmssyWebhook(options) {
1703
+ async function verifyCmssyWebhook(options) {
1670
1704
  const { body, signatureHeader, secret } = options;
1671
1705
  if (!signatureHeader) {
1672
1706
  throw new CmssyWebhookError("Missing X-Cmssy-Signature header");
@@ -1680,7 +1714,7 @@ function verifyCmssyWebhook(options) {
1680
1714
  if (Math.abs(now - timestamp) > toleranceMs) {
1681
1715
  throw new CmssyWebhookError("Webhook timestamp outside tolerance");
1682
1716
  }
1683
- const expected = createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
1717
+ const expected = await hmacSha256Hex(secret, `${timestamp}.${body}`);
1684
1718
  if (!timingSafeHexEqual(expected, signature)) {
1685
1719
  throw new CmssyWebhookError("Webhook signature mismatch");
1686
1720
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/core",
3
- "version": "5.1.0",
3
+ "version": "6.0.1",
4
4
  "description": "Framework-agnostic cmssy client: content, commerce, config, editor protocol. No React, no Next.",
5
5
  "keywords": [
6
6
  "cmssy",