@astrasyncai/verification-gateway 2.2.0 → 2.3.4

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.
Files changed (84) hide show
  1. package/README.md +64 -30
  2. package/dist/adapter-interface/interface.d.mts +2 -2
  3. package/dist/adapter-interface/interface.d.ts +2 -2
  4. package/dist/adapters/express.d.mts +2 -2
  5. package/dist/adapters/express.d.ts +2 -2
  6. package/dist/adapters/express.js +40 -89
  7. package/dist/adapters/express.js.map +1 -1
  8. package/dist/adapters/express.mjs +40 -89
  9. package/dist/adapters/express.mjs.map +1 -1
  10. package/dist/adapters/nextjs.d.mts +2 -2
  11. package/dist/adapters/nextjs.d.ts +2 -2
  12. package/dist/adapters/nextjs.js +39 -109
  13. package/dist/adapters/nextjs.js.map +1 -1
  14. package/dist/adapters/nextjs.mjs +39 -109
  15. package/dist/adapters/nextjs.mjs.map +1 -1
  16. package/dist/adapters/sdk.d.mts +2 -2
  17. package/dist/adapters/sdk.d.ts +2 -2
  18. package/dist/adapters/sdk.js +39 -53
  19. package/dist/adapters/sdk.js.map +1 -1
  20. package/dist/adapters/sdk.mjs +39 -53
  21. package/dist/adapters/sdk.mjs.map +1 -1
  22. package/dist/agent/index.d.mts +2 -2
  23. package/dist/agent/index.d.ts +2 -2
  24. package/dist/agent/index.js +2 -2
  25. package/dist/agent/index.js.map +1 -1
  26. package/dist/agent/index.mjs +2 -2
  27. package/dist/agent/index.mjs.map +1 -1
  28. package/dist/browser/background.js +39 -53
  29. package/dist/browser/background.js.map +1 -1
  30. package/dist/browser/background.mjs +39 -53
  31. package/dist/browser/background.mjs.map +1 -1
  32. package/dist/browser/browser-adapter.d.mts +2 -2
  33. package/dist/browser/browser-adapter.d.ts +2 -2
  34. package/dist/cli/index.d.mts +2 -2
  35. package/dist/cli/index.d.ts +2 -2
  36. package/dist/cursor/cursor-adapter.d.mts +2 -2
  37. package/dist/cursor/cursor-adapter.d.ts +2 -2
  38. package/dist/cursor/extension.d.mts +2 -2
  39. package/dist/cursor/extension.d.ts +2 -2
  40. package/dist/cursor/extension.js +39 -53
  41. package/dist/cursor/extension.js.map +1 -1
  42. package/dist/cursor/extension.mjs +39 -53
  43. package/dist/cursor/extension.mjs.map +1 -1
  44. package/dist/{express-DpwYW08E.d.ts → express-CraCA8_t.d.ts} +2 -2
  45. package/dist/{express-C9KqJNWV.d.mts → express-DtvJ6BGt.d.mts} +2 -2
  46. package/dist/gateway/gateway.d.mts +2 -2
  47. package/dist/gateway/gateway.d.ts +2 -2
  48. package/dist/gateway/gateway.js +39 -53
  49. package/dist/gateway/gateway.js.map +1 -1
  50. package/dist/gateway/gateway.mjs +39 -53
  51. package/dist/gateway/gateway.mjs.map +1 -1
  52. package/dist/git-trigger/git-hooks.d.mts +2 -2
  53. package/dist/git-trigger/git-hooks.d.ts +2 -2
  54. package/dist/{index-gM-lgX_X.d.ts → index--KzVRa32.d.ts} +1 -1
  55. package/dist/{index-BMZdjGT4.d.mts → index-BZ85CeEr.d.mts} +2 -2
  56. package/dist/{index-Dm2xA6j1.d.ts → index-BzAFmemy.d.ts} +2 -2
  57. package/dist/{index-DlsYN3Et.d.mts → index-SEgnWzkf.d.mts} +1 -1
  58. package/dist/index.d.mts +7 -7
  59. package/dist/index.d.ts +7 -7
  60. package/dist/index.js +42 -107
  61. package/dist/index.js.map +1 -1
  62. package/dist/index.mjs +42 -107
  63. package/dist/index.mjs.map +1 -1
  64. package/dist/local-evaluator/evaluator.d.mts +2 -2
  65. package/dist/local-evaluator/evaluator.d.ts +2 -2
  66. package/dist/{nextjs-yNzimC3a.d.ts → nextjs-B8o9C0t6.d.ts} +1 -1
  67. package/dist/{nextjs-BEqidT0U.d.mts → nextjs-DZHAn9j-.d.mts} +1 -1
  68. package/dist/{sdk-CP9C9Qu0.d.ts → sdk-BQ3olp3v.d.ts} +2 -2
  69. package/dist/{sdk-7fa9H0qa.d.mts → sdk-CRSUFQH2.d.mts} +2 -2
  70. package/dist/transport/index.d.mts +2 -2
  71. package/dist/transport/index.d.ts +2 -2
  72. package/dist/{types-CrVMq_Td.d.mts → types-JMgPake9.d.mts} +135 -28
  73. package/dist/{types-CrVMq_Td.d.ts → types-JMgPake9.d.ts} +135 -28
  74. package/dist/{types-DE0ooQJ6.d.mts → types-aN1UHhyy.d.mts} +1 -1
  75. package/dist/{types-rigu2bH3.d.ts → types-osMd_dpT.d.ts} +1 -1
  76. package/dist/ui/index.d.mts +1 -1
  77. package/dist/ui/index.d.ts +1 -1
  78. package/dist/webhooks.d.mts +59 -0
  79. package/dist/webhooks.d.ts +59 -0
  80. package/dist/webhooks.js +81 -0
  81. package/dist/webhooks.js.map +1 -0
  82. package/dist/webhooks.mjs +55 -0
  83. package/dist/webhooks.mjs.map +1 -0
  84. package/package.json +6 -1
@@ -0,0 +1,59 @@
1
+ /**
2
+ * AstraSync webhook signature verification.
3
+ *
4
+ * AstraSync signs every webhook delivery with HMAC-SHA256 over
5
+ * `${unix_timestamp}.${rawBody}` using the merchant's webhook secret
6
+ * (returned ONCE at endpoint registration). The signature is sent in
7
+ * the `X-AstraSync-Signature` header in the form:
8
+ *
9
+ * X-AstraSync-Signature: t=<unix-ts>,v1=<hex-hmac>
10
+ *
11
+ * This pattern mirrors Stripe's `Stripe-Signature` to ease adoption.
12
+ *
13
+ * Usage:
14
+ * import { verifyAstraSyncWebhook } from '@astrasyncai/verification-gateway/webhooks';
15
+ *
16
+ * app.post('/webhooks/astrasync', express.raw({type:'application/json'}), (req, res) => {
17
+ * const result = verifyAstraSyncWebhook(req.body.toString('utf8'), req.headers, secret);
18
+ * if (!result.ok) return res.status(401).json({error: result.reason});
19
+ * // ... process event
20
+ * });
21
+ */
22
+ interface VerifyWebhookOptions {
23
+ /**
24
+ * Maximum age (seconds) for the signature timestamp. Older deliveries
25
+ * are rejected as replays. Default 300 (5 minutes) — matches Stripe.
26
+ */
27
+ toleranceSeconds?: number;
28
+ /**
29
+ * Override "now" for tests. Defaults to Date.now().
30
+ */
31
+ nowMs?: number;
32
+ }
33
+ interface VerifyWebhookResult {
34
+ ok: boolean;
35
+ reason?: string;
36
+ }
37
+ /**
38
+ * Verify an AstraSync-issued webhook delivery.
39
+ *
40
+ * @param rawBody - The raw request body as a string (NOT the parsed JSON).
41
+ * Use `express.raw({type:'application/json'})` to preserve bytes.
42
+ * @param headers - Incoming request headers (case-insensitive).
43
+ * @param secret - The merchant's webhook secret from endpoint registration.
44
+ * @param options - Optional tolerance overrides.
45
+ * @returns - `{ok: true}` on success, `{ok: false, reason}` on failure.
46
+ */
47
+ declare function verifyAstraSyncWebhook(rawBody: string, headers: Record<string, string | string[] | undefined>, secret: string, options?: VerifyWebhookOptions): VerifyWebhookResult;
48
+ /**
49
+ * Server-side companion: produce an `X-AstraSync-Signature` header value for
50
+ * an outbound webhook delivery. Exposed for completeness and for test
51
+ * harnesses that want to verify the verifier; the AstraSync platform itself
52
+ * uses an internal version of the same logic.
53
+ */
54
+ declare function signAstraSyncWebhook(rawBody: string, secret: string, nowMs?: number): {
55
+ header: string;
56
+ timestamp: number;
57
+ };
58
+
59
+ export { type VerifyWebhookOptions, type VerifyWebhookResult, signAstraSyncWebhook, verifyAstraSyncWebhook };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * AstraSync webhook signature verification.
3
+ *
4
+ * AstraSync signs every webhook delivery with HMAC-SHA256 over
5
+ * `${unix_timestamp}.${rawBody}` using the merchant's webhook secret
6
+ * (returned ONCE at endpoint registration). The signature is sent in
7
+ * the `X-AstraSync-Signature` header in the form:
8
+ *
9
+ * X-AstraSync-Signature: t=<unix-ts>,v1=<hex-hmac>
10
+ *
11
+ * This pattern mirrors Stripe's `Stripe-Signature` to ease adoption.
12
+ *
13
+ * Usage:
14
+ * import { verifyAstraSyncWebhook } from '@astrasyncai/verification-gateway/webhooks';
15
+ *
16
+ * app.post('/webhooks/astrasync', express.raw({type:'application/json'}), (req, res) => {
17
+ * const result = verifyAstraSyncWebhook(req.body.toString('utf8'), req.headers, secret);
18
+ * if (!result.ok) return res.status(401).json({error: result.reason});
19
+ * // ... process event
20
+ * });
21
+ */
22
+ interface VerifyWebhookOptions {
23
+ /**
24
+ * Maximum age (seconds) for the signature timestamp. Older deliveries
25
+ * are rejected as replays. Default 300 (5 minutes) — matches Stripe.
26
+ */
27
+ toleranceSeconds?: number;
28
+ /**
29
+ * Override "now" for tests. Defaults to Date.now().
30
+ */
31
+ nowMs?: number;
32
+ }
33
+ interface VerifyWebhookResult {
34
+ ok: boolean;
35
+ reason?: string;
36
+ }
37
+ /**
38
+ * Verify an AstraSync-issued webhook delivery.
39
+ *
40
+ * @param rawBody - The raw request body as a string (NOT the parsed JSON).
41
+ * Use `express.raw({type:'application/json'})` to preserve bytes.
42
+ * @param headers - Incoming request headers (case-insensitive).
43
+ * @param secret - The merchant's webhook secret from endpoint registration.
44
+ * @param options - Optional tolerance overrides.
45
+ * @returns - `{ok: true}` on success, `{ok: false, reason}` on failure.
46
+ */
47
+ declare function verifyAstraSyncWebhook(rawBody: string, headers: Record<string, string | string[] | undefined>, secret: string, options?: VerifyWebhookOptions): VerifyWebhookResult;
48
+ /**
49
+ * Server-side companion: produce an `X-AstraSync-Signature` header value for
50
+ * an outbound webhook delivery. Exposed for completeness and for test
51
+ * harnesses that want to verify the verifier; the AstraSync platform itself
52
+ * uses an internal version of the same logic.
53
+ */
54
+ declare function signAstraSyncWebhook(rawBody: string, secret: string, nowMs?: number): {
55
+ header: string;
56
+ timestamp: number;
57
+ };
58
+
59
+ export { type VerifyWebhookOptions, type VerifyWebhookResult, signAstraSyncWebhook, verifyAstraSyncWebhook };
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/webhooks.ts
21
+ var webhooks_exports = {};
22
+ __export(webhooks_exports, {
23
+ signAstraSyncWebhook: () => signAstraSyncWebhook,
24
+ verifyAstraSyncWebhook: () => verifyAstraSyncWebhook
25
+ });
26
+ module.exports = __toCommonJS(webhooks_exports);
27
+ var import_crypto = require("crypto");
28
+ var DEFAULT_TOLERANCE_SECONDS = 300;
29
+ function parseSignatureHeader(header) {
30
+ const parts = {};
31
+ for (const segment of header.split(",")) {
32
+ const [k, v] = segment.split("=");
33
+ if (k === "t" || k === "v1") parts[k] = v;
34
+ }
35
+ return parts;
36
+ }
37
+ function computeHmac(secret, signedPayload) {
38
+ return (0, import_crypto.createHmac)("sha256", secret).update(signedPayload, "utf8").digest("hex");
39
+ }
40
+ function constantTimeEquals(a, b) {
41
+ const aBuf = Buffer.from(a, "utf8");
42
+ const bBuf = Buffer.from(b, "utf8");
43
+ if (aBuf.length !== bBuf.length) return false;
44
+ return (0, import_crypto.timingSafeEqual)(aBuf, bBuf);
45
+ }
46
+ function verifyAstraSyncWebhook(rawBody, headers, secret, options = {}) {
47
+ if (!secret) return { ok: false, reason: "no_secret_provided" };
48
+ const rawHeader = headers["x-astrasync-signature"] ?? headers["X-Astrasync-Signature"] ?? headers["X-AstraSync-Signature"] ?? headers["X-ASTRASYNC-SIGNATURE"];
49
+ if (!rawHeader) return { ok: false, reason: "missing_signature_header" };
50
+ const headerValue = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;
51
+ const { t: timestamp, v1: receivedSignature } = parseSignatureHeader(headerValue);
52
+ if (!timestamp || !receivedSignature) {
53
+ return { ok: false, reason: "malformed_signature_header" };
54
+ }
55
+ const tolerance = options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
56
+ const now = options.nowMs ?? Date.now();
57
+ const tsSeconds = Number(timestamp);
58
+ if (!Number.isFinite(tsSeconds)) {
59
+ return { ok: false, reason: "invalid_timestamp" };
60
+ }
61
+ const ageSeconds = Math.abs(now / 1e3 - tsSeconds);
62
+ if (ageSeconds > tolerance) {
63
+ return { ok: false, reason: "timestamp_outside_tolerance" };
64
+ }
65
+ const expectedSignature = computeHmac(secret, `${timestamp}.${rawBody}`);
66
+ if (!constantTimeEquals(expectedSignature, receivedSignature)) {
67
+ return { ok: false, reason: "signature_mismatch" };
68
+ }
69
+ return { ok: true };
70
+ }
71
+ function signAstraSyncWebhook(rawBody, secret, nowMs = Date.now()) {
72
+ const timestamp = Math.floor(nowMs / 1e3);
73
+ const v1 = computeHmac(secret, `${timestamp}.${rawBody}`);
74
+ return { header: `t=${timestamp},v1=${v1}`, timestamp };
75
+ }
76
+ // Annotate the CommonJS export names for ESM import in node:
77
+ 0 && (module.exports = {
78
+ signAstraSyncWebhook,
79
+ verifyAstraSyncWebhook
80
+ });
81
+ //# sourceMappingURL=webhooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/webhooks.ts"],"sourcesContent":["/**\n * AstraSync webhook signature verification.\n *\n * AstraSync signs every webhook delivery with HMAC-SHA256 over\n * `${unix_timestamp}.${rawBody}` using the merchant's webhook secret\n * (returned ONCE at endpoint registration). The signature is sent in\n * the `X-AstraSync-Signature` header in the form:\n *\n * X-AstraSync-Signature: t=<unix-ts>,v1=<hex-hmac>\n *\n * This pattern mirrors Stripe's `Stripe-Signature` to ease adoption.\n *\n * Usage:\n * import { verifyAstraSyncWebhook } from '@astrasyncai/verification-gateway/webhooks';\n *\n * app.post('/webhooks/astrasync', express.raw({type:'application/json'}), (req, res) => {\n * const result = verifyAstraSyncWebhook(req.body.toString('utf8'), req.headers, secret);\n * if (!result.ok) return res.status(401).json({error: result.reason});\n * // ... process event\n * });\n */\n\nimport { createHmac, timingSafeEqual } from 'crypto';\n\nexport interface VerifyWebhookOptions {\n /**\n * Maximum age (seconds) for the signature timestamp. Older deliveries\n * are rejected as replays. Default 300 (5 minutes) — matches Stripe.\n */\n toleranceSeconds?: number;\n /**\n * Override \"now\" for tests. Defaults to Date.now().\n */\n nowMs?: number;\n}\n\nexport interface VerifyWebhookResult {\n ok: boolean;\n reason?: string;\n}\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nfunction parseSignatureHeader(header: string): { t?: string; v1?: string } {\n const parts: { t?: string; v1?: string } = {};\n for (const segment of header.split(',')) {\n const [k, v] = segment.split('=');\n if (k === 't' || k === 'v1') parts[k] = v;\n }\n return parts;\n}\n\nfunction computeHmac(secret: string, signedPayload: string): string {\n return createHmac('sha256', secret).update(signedPayload, 'utf8').digest('hex');\n}\n\nfunction constantTimeEquals(a: string, b: string): boolean {\n const aBuf = Buffer.from(a, 'utf8');\n const bBuf = Buffer.from(b, 'utf8');\n if (aBuf.length !== bBuf.length) return false;\n return timingSafeEqual(aBuf, bBuf);\n}\n\n/**\n * Verify an AstraSync-issued webhook delivery.\n *\n * @param rawBody - The raw request body as a string (NOT the parsed JSON).\n * Use `express.raw({type:'application/json'})` to preserve bytes.\n * @param headers - Incoming request headers (case-insensitive).\n * @param secret - The merchant's webhook secret from endpoint registration.\n * @param options - Optional tolerance overrides.\n * @returns - `{ok: true}` on success, `{ok: false, reason}` on failure.\n */\nexport function verifyAstraSyncWebhook(\n rawBody: string,\n headers: Record<string, string | string[] | undefined>,\n secret: string,\n options: VerifyWebhookOptions = {}\n): VerifyWebhookResult {\n if (!secret) return { ok: false, reason: 'no_secret_provided' };\n\n // Header lookup is case-insensitive — support common variants.\n const rawHeader =\n headers['x-astrasync-signature'] ??\n headers['X-Astrasync-Signature'] ??\n headers['X-AstraSync-Signature'] ??\n headers['X-ASTRASYNC-SIGNATURE'];\n if (!rawHeader) return { ok: false, reason: 'missing_signature_header' };\n\n const headerValue = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;\n const { t: timestamp, v1: receivedSignature } = parseSignatureHeader(headerValue);\n if (!timestamp || !receivedSignature) {\n return { ok: false, reason: 'malformed_signature_header' };\n }\n\n const tolerance = options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;\n const now = options.nowMs ?? Date.now();\n const tsSeconds = Number(timestamp);\n if (!Number.isFinite(tsSeconds)) {\n return { ok: false, reason: 'invalid_timestamp' };\n }\n const ageSeconds = Math.abs(now / 1000 - tsSeconds);\n if (ageSeconds > tolerance) {\n return { ok: false, reason: 'timestamp_outside_tolerance' };\n }\n\n const expectedSignature = computeHmac(secret, `${timestamp}.${rawBody}`);\n if (!constantTimeEquals(expectedSignature, receivedSignature)) {\n return { ok: false, reason: 'signature_mismatch' };\n }\n\n return { ok: true };\n}\n\n/**\n * Server-side companion: produce an `X-AstraSync-Signature` header value for\n * an outbound webhook delivery. Exposed for completeness and for test\n * harnesses that want to verify the verifier; the AstraSync platform itself\n * uses an internal version of the same logic.\n */\nexport function signAstraSyncWebhook(\n rawBody: string,\n secret: string,\n nowMs: number = Date.now()\n): { header: string; timestamp: number } {\n const timestamp = Math.floor(nowMs / 1000);\n const v1 = computeHmac(secret, `${timestamp}.${rawBody}`);\n return { header: `t=${timestamp},v1=${v1}`, timestamp };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA,oBAA4C;AAmB5C,IAAM,4BAA4B;AAElC,SAAS,qBAAqB,QAA6C;AACzE,QAAM,QAAqC,CAAC;AAC5C,aAAW,WAAW,OAAO,MAAM,GAAG,GAAG;AACvC,UAAM,CAAC,GAAG,CAAC,IAAI,QAAQ,MAAM,GAAG;AAChC,QAAI,MAAM,OAAO,MAAM,KAAM,OAAM,CAAC,IAAI;AAAA,EAC1C;AACA,SAAO;AACT;AAEA,SAAS,YAAY,QAAgB,eAA+B;AAClE,aAAO,0BAAW,UAAU,MAAM,EAAE,OAAO,eAAe,MAAM,EAAE,OAAO,KAAK;AAChF;AAEA,SAAS,mBAAmB,GAAW,GAAoB;AACzD,QAAM,OAAO,OAAO,KAAK,GAAG,MAAM;AAClC,QAAM,OAAO,OAAO,KAAK,GAAG,MAAM;AAClC,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AACxC,aAAO,+BAAgB,MAAM,IAAI;AACnC;AAYO,SAAS,uBACd,SACA,SACA,QACA,UAAgC,CAAC,GACZ;AACrB,MAAI,CAAC,OAAQ,QAAO,EAAE,IAAI,OAAO,QAAQ,qBAAqB;AAG9D,QAAM,YACJ,QAAQ,uBAAuB,KAC/B,QAAQ,uBAAuB,KAC/B,QAAQ,uBAAuB,KAC/B,QAAQ,uBAAuB;AACjC,MAAI,CAAC,UAAW,QAAO,EAAE,IAAI,OAAO,QAAQ,2BAA2B;AAEvE,QAAM,cAAc,MAAM,QAAQ,SAAS,IAAI,UAAU,CAAC,IAAI;AAC9D,QAAM,EAAE,GAAG,WAAW,IAAI,kBAAkB,IAAI,qBAAqB,WAAW;AAChF,MAAI,CAAC,aAAa,CAAC,mBAAmB;AACpC,WAAO,EAAE,IAAI,OAAO,QAAQ,6BAA6B;AAAA,EAC3D;AAEA,QAAM,YAAY,QAAQ,oBAAoB;AAC9C,QAAM,MAAM,QAAQ,SAAS,KAAK,IAAI;AACtC,QAAM,YAAY,OAAO,SAAS;AAClC,MAAI,CAAC,OAAO,SAAS,SAAS,GAAG;AAC/B,WAAO,EAAE,IAAI,OAAO,QAAQ,oBAAoB;AAAA,EAClD;AACA,QAAM,aAAa,KAAK,IAAI,MAAM,MAAO,SAAS;AAClD,MAAI,aAAa,WAAW;AAC1B,WAAO,EAAE,IAAI,OAAO,QAAQ,8BAA8B;AAAA,EAC5D;AAEA,QAAM,oBAAoB,YAAY,QAAQ,GAAG,SAAS,IAAI,OAAO,EAAE;AACvE,MAAI,CAAC,mBAAmB,mBAAmB,iBAAiB,GAAG;AAC7D,WAAO,EAAE,IAAI,OAAO,QAAQ,qBAAqB;AAAA,EACnD;AAEA,SAAO,EAAE,IAAI,KAAK;AACpB;AAQO,SAAS,qBACd,SACA,QACA,QAAgB,KAAK,IAAI,GACc;AACvC,QAAM,YAAY,KAAK,MAAM,QAAQ,GAAI;AACzC,QAAM,KAAK,YAAY,QAAQ,GAAG,SAAS,IAAI,OAAO,EAAE;AACxD,SAAO,EAAE,QAAQ,KAAK,SAAS,OAAO,EAAE,IAAI,UAAU;AACxD;","names":[]}
@@ -0,0 +1,55 @@
1
+ // src/webhooks.ts
2
+ import { createHmac, timingSafeEqual } from "crypto";
3
+ var DEFAULT_TOLERANCE_SECONDS = 300;
4
+ function parseSignatureHeader(header) {
5
+ const parts = {};
6
+ for (const segment of header.split(",")) {
7
+ const [k, v] = segment.split("=");
8
+ if (k === "t" || k === "v1") parts[k] = v;
9
+ }
10
+ return parts;
11
+ }
12
+ function computeHmac(secret, signedPayload) {
13
+ return createHmac("sha256", secret).update(signedPayload, "utf8").digest("hex");
14
+ }
15
+ function constantTimeEquals(a, b) {
16
+ const aBuf = Buffer.from(a, "utf8");
17
+ const bBuf = Buffer.from(b, "utf8");
18
+ if (aBuf.length !== bBuf.length) return false;
19
+ return timingSafeEqual(aBuf, bBuf);
20
+ }
21
+ function verifyAstraSyncWebhook(rawBody, headers, secret, options = {}) {
22
+ if (!secret) return { ok: false, reason: "no_secret_provided" };
23
+ const rawHeader = headers["x-astrasync-signature"] ?? headers["X-Astrasync-Signature"] ?? headers["X-AstraSync-Signature"] ?? headers["X-ASTRASYNC-SIGNATURE"];
24
+ if (!rawHeader) return { ok: false, reason: "missing_signature_header" };
25
+ const headerValue = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;
26
+ const { t: timestamp, v1: receivedSignature } = parseSignatureHeader(headerValue);
27
+ if (!timestamp || !receivedSignature) {
28
+ return { ok: false, reason: "malformed_signature_header" };
29
+ }
30
+ const tolerance = options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
31
+ const now = options.nowMs ?? Date.now();
32
+ const tsSeconds = Number(timestamp);
33
+ if (!Number.isFinite(tsSeconds)) {
34
+ return { ok: false, reason: "invalid_timestamp" };
35
+ }
36
+ const ageSeconds = Math.abs(now / 1e3 - tsSeconds);
37
+ if (ageSeconds > tolerance) {
38
+ return { ok: false, reason: "timestamp_outside_tolerance" };
39
+ }
40
+ const expectedSignature = computeHmac(secret, `${timestamp}.${rawBody}`);
41
+ if (!constantTimeEquals(expectedSignature, receivedSignature)) {
42
+ return { ok: false, reason: "signature_mismatch" };
43
+ }
44
+ return { ok: true };
45
+ }
46
+ function signAstraSyncWebhook(rawBody, secret, nowMs = Date.now()) {
47
+ const timestamp = Math.floor(nowMs / 1e3);
48
+ const v1 = computeHmac(secret, `${timestamp}.${rawBody}`);
49
+ return { header: `t=${timestamp},v1=${v1}`, timestamp };
50
+ }
51
+ export {
52
+ signAstraSyncWebhook,
53
+ verifyAstraSyncWebhook
54
+ };
55
+ //# sourceMappingURL=webhooks.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/webhooks.ts"],"sourcesContent":["/**\n * AstraSync webhook signature verification.\n *\n * AstraSync signs every webhook delivery with HMAC-SHA256 over\n * `${unix_timestamp}.${rawBody}` using the merchant's webhook secret\n * (returned ONCE at endpoint registration). The signature is sent in\n * the `X-AstraSync-Signature` header in the form:\n *\n * X-AstraSync-Signature: t=<unix-ts>,v1=<hex-hmac>\n *\n * This pattern mirrors Stripe's `Stripe-Signature` to ease adoption.\n *\n * Usage:\n * import { verifyAstraSyncWebhook } from '@astrasyncai/verification-gateway/webhooks';\n *\n * app.post('/webhooks/astrasync', express.raw({type:'application/json'}), (req, res) => {\n * const result = verifyAstraSyncWebhook(req.body.toString('utf8'), req.headers, secret);\n * if (!result.ok) return res.status(401).json({error: result.reason});\n * // ... process event\n * });\n */\n\nimport { createHmac, timingSafeEqual } from 'crypto';\n\nexport interface VerifyWebhookOptions {\n /**\n * Maximum age (seconds) for the signature timestamp. Older deliveries\n * are rejected as replays. Default 300 (5 minutes) — matches Stripe.\n */\n toleranceSeconds?: number;\n /**\n * Override \"now\" for tests. Defaults to Date.now().\n */\n nowMs?: number;\n}\n\nexport interface VerifyWebhookResult {\n ok: boolean;\n reason?: string;\n}\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nfunction parseSignatureHeader(header: string): { t?: string; v1?: string } {\n const parts: { t?: string; v1?: string } = {};\n for (const segment of header.split(',')) {\n const [k, v] = segment.split('=');\n if (k === 't' || k === 'v1') parts[k] = v;\n }\n return parts;\n}\n\nfunction computeHmac(secret: string, signedPayload: string): string {\n return createHmac('sha256', secret).update(signedPayload, 'utf8').digest('hex');\n}\n\nfunction constantTimeEquals(a: string, b: string): boolean {\n const aBuf = Buffer.from(a, 'utf8');\n const bBuf = Buffer.from(b, 'utf8');\n if (aBuf.length !== bBuf.length) return false;\n return timingSafeEqual(aBuf, bBuf);\n}\n\n/**\n * Verify an AstraSync-issued webhook delivery.\n *\n * @param rawBody - The raw request body as a string (NOT the parsed JSON).\n * Use `express.raw({type:'application/json'})` to preserve bytes.\n * @param headers - Incoming request headers (case-insensitive).\n * @param secret - The merchant's webhook secret from endpoint registration.\n * @param options - Optional tolerance overrides.\n * @returns - `{ok: true}` on success, `{ok: false, reason}` on failure.\n */\nexport function verifyAstraSyncWebhook(\n rawBody: string,\n headers: Record<string, string | string[] | undefined>,\n secret: string,\n options: VerifyWebhookOptions = {}\n): VerifyWebhookResult {\n if (!secret) return { ok: false, reason: 'no_secret_provided' };\n\n // Header lookup is case-insensitive — support common variants.\n const rawHeader =\n headers['x-astrasync-signature'] ??\n headers['X-Astrasync-Signature'] ??\n headers['X-AstraSync-Signature'] ??\n headers['X-ASTRASYNC-SIGNATURE'];\n if (!rawHeader) return { ok: false, reason: 'missing_signature_header' };\n\n const headerValue = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;\n const { t: timestamp, v1: receivedSignature } = parseSignatureHeader(headerValue);\n if (!timestamp || !receivedSignature) {\n return { ok: false, reason: 'malformed_signature_header' };\n }\n\n const tolerance = options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;\n const now = options.nowMs ?? Date.now();\n const tsSeconds = Number(timestamp);\n if (!Number.isFinite(tsSeconds)) {\n return { ok: false, reason: 'invalid_timestamp' };\n }\n const ageSeconds = Math.abs(now / 1000 - tsSeconds);\n if (ageSeconds > tolerance) {\n return { ok: false, reason: 'timestamp_outside_tolerance' };\n }\n\n const expectedSignature = computeHmac(secret, `${timestamp}.${rawBody}`);\n if (!constantTimeEquals(expectedSignature, receivedSignature)) {\n return { ok: false, reason: 'signature_mismatch' };\n }\n\n return { ok: true };\n}\n\n/**\n * Server-side companion: produce an `X-AstraSync-Signature` header value for\n * an outbound webhook delivery. Exposed for completeness and for test\n * harnesses that want to verify the verifier; the AstraSync platform itself\n * uses an internal version of the same logic.\n */\nexport function signAstraSyncWebhook(\n rawBody: string,\n secret: string,\n nowMs: number = Date.now()\n): { header: string; timestamp: number } {\n const timestamp = Math.floor(nowMs / 1000);\n const v1 = computeHmac(secret, `${timestamp}.${rawBody}`);\n return { header: `t=${timestamp},v1=${v1}`, timestamp };\n}\n"],"mappings":";AAsBA,SAAS,YAAY,uBAAuB;AAmB5C,IAAM,4BAA4B;AAElC,SAAS,qBAAqB,QAA6C;AACzE,QAAM,QAAqC,CAAC;AAC5C,aAAW,WAAW,OAAO,MAAM,GAAG,GAAG;AACvC,UAAM,CAAC,GAAG,CAAC,IAAI,QAAQ,MAAM,GAAG;AAChC,QAAI,MAAM,OAAO,MAAM,KAAM,OAAM,CAAC,IAAI;AAAA,EAC1C;AACA,SAAO;AACT;AAEA,SAAS,YAAY,QAAgB,eAA+B;AAClE,SAAO,WAAW,UAAU,MAAM,EAAE,OAAO,eAAe,MAAM,EAAE,OAAO,KAAK;AAChF;AAEA,SAAS,mBAAmB,GAAW,GAAoB;AACzD,QAAM,OAAO,OAAO,KAAK,GAAG,MAAM;AAClC,QAAM,OAAO,OAAO,KAAK,GAAG,MAAM;AAClC,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AACxC,SAAO,gBAAgB,MAAM,IAAI;AACnC;AAYO,SAAS,uBACd,SACA,SACA,QACA,UAAgC,CAAC,GACZ;AACrB,MAAI,CAAC,OAAQ,QAAO,EAAE,IAAI,OAAO,QAAQ,qBAAqB;AAG9D,QAAM,YACJ,QAAQ,uBAAuB,KAC/B,QAAQ,uBAAuB,KAC/B,QAAQ,uBAAuB,KAC/B,QAAQ,uBAAuB;AACjC,MAAI,CAAC,UAAW,QAAO,EAAE,IAAI,OAAO,QAAQ,2BAA2B;AAEvE,QAAM,cAAc,MAAM,QAAQ,SAAS,IAAI,UAAU,CAAC,IAAI;AAC9D,QAAM,EAAE,GAAG,WAAW,IAAI,kBAAkB,IAAI,qBAAqB,WAAW;AAChF,MAAI,CAAC,aAAa,CAAC,mBAAmB;AACpC,WAAO,EAAE,IAAI,OAAO,QAAQ,6BAA6B;AAAA,EAC3D;AAEA,QAAM,YAAY,QAAQ,oBAAoB;AAC9C,QAAM,MAAM,QAAQ,SAAS,KAAK,IAAI;AACtC,QAAM,YAAY,OAAO,SAAS;AAClC,MAAI,CAAC,OAAO,SAAS,SAAS,GAAG;AAC/B,WAAO,EAAE,IAAI,OAAO,QAAQ,oBAAoB;AAAA,EAClD;AACA,QAAM,aAAa,KAAK,IAAI,MAAM,MAAO,SAAS;AAClD,MAAI,aAAa,WAAW;AAC1B,WAAO,EAAE,IAAI,OAAO,QAAQ,8BAA8B;AAAA,EAC5D;AAEA,QAAM,oBAAoB,YAAY,QAAQ,GAAG,SAAS,IAAI,OAAO,EAAE;AACvE,MAAI,CAAC,mBAAmB,mBAAmB,iBAAiB,GAAG;AAC7D,WAAO,EAAE,IAAI,OAAO,QAAQ,qBAAqB;AAAA,EACnD;AAEA,SAAO,EAAE,IAAI,KAAK;AACpB;AAQO,SAAS,qBACd,SACA,QACA,QAAgB,KAAK,IAAI,GACc;AACvC,QAAM,YAAY,KAAK,MAAM,QAAQ,GAAI;AACzC,QAAM,KAAK,YAAY,QAAQ,GAAG,SAAS,IAAI,OAAO,EAAE;AACxD,SAAO,EAAE,QAAQ,KAAK,SAAS,OAAO,EAAE,IAAI,UAAU;AACxD;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrasyncai/verification-gateway",
3
- "version": "2.2.0",
3
+ "version": "2.3.4",
4
4
  "description": "Universal Verification Gateway for AstraSync KYA Platform - verify AI agents across any counterparty type",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -75,6 +75,11 @@
75
75
  "types": "./dist/git-trigger/git-hooks.d.ts",
76
76
  "import": "./dist/git-trigger/git-hooks.mjs",
77
77
  "require": "./dist/git-trigger/git-hooks.js"
78
+ },
79
+ "./webhooks": {
80
+ "types": "./dist/webhooks.d.ts",
81
+ "import": "./dist/webhooks.mjs",
82
+ "require": "./dist/webhooks.js"
78
83
  }
79
84
  },
80
85
  "files": [