@openwop/openwop 1.8.0 → 1.9.0
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 +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/webhook-header-families.d.ts +33 -0
- package/dist/webhook-header-families.d.ts.map +1 -0
- package/dist/webhook-header-families.js +55 -0
- package/dist/webhook-header-families.js.map +1 -0
- package/dist/webhook-helpers.browser.d.ts +2 -1
- package/dist/webhook-helpers.browser.d.ts.map +1 -1
- package/dist/webhook-helpers.browser.js +4 -0
- package/dist/webhook-helpers.browser.js.map +1 -1
- package/dist/webhook-helpers.d.ts +26 -10
- package/dist/webhook-helpers.d.ts.map +1 -1
- package/dist/webhook-helpers.js +32 -10
- package/dist/webhook-helpers.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +5 -0
- package/src/webhook-header-families.ts +66 -0
- package/src/webhook-helpers.browser.ts +6 -1
- package/src/webhook-helpers.ts +45 -11
package/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,9 @@ export { OPENWOP_COST_ATTRIBUTE_NAMES, sanitizeCostAttributes, } from './cost-at
|
|
|
23
23
|
export type { OpenwopCostAttributeName } from './cost-attribution.js';
|
|
24
24
|
/** @deprecated Import from `@openwop/openwop/webhooks` — server-only; removed from the barrel in the next major. */
|
|
25
25
|
export { DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS, verifyWebhookSignature, signWebhookDelivery, } from './webhook-helpers.js';
|
|
26
|
-
export type { VerifyWebhookSignatureOptions, VerifyWebhookOutcome, } from './webhook-helpers.js';
|
|
26
|
+
export type { VerifyWebhookSignatureOptions, VerifyWebhookOutcome, SignedWebhookDelivery, } from './webhook-helpers.js';
|
|
27
|
+
export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
|
|
28
|
+
export type { WebhookHeaderRead } from './webhook-header-families.js';
|
|
27
29
|
export { RegistryClient } from './registry-helpers.js';
|
|
28
30
|
export type { RegistryClientOptions, RegistryDiscovery, RegistryIndex, RegistryIndexEntry, RegistryPackMetadata, RegistryVersionManifest, } from './registry-helpers.js';
|
|
29
31
|
export type { A2UISurfacePayload, A2uiSurfaceDeltaFrame, A2uiSurfacePatchOp, AIEnvelope, AIEnvelopeErrorPayload, ClarificationRequestPayload, EnvelopeContract, EnvelopeContractRefusal, EnvelopeContractsCapability, EnvelopeMeta, EnvelopeOutcome, EnvelopeStrictness, PartialInfo, SchemaRequestPayload, SchemaResponsePayload, ValidationDetail, LocalizedContentStatus, LocalizedContentPage, LocalizedContentSection, LocalizedContentPageResponse, LocalizedContentLanguageSettings, PutContentSectionRequest, TriggerSubscriptionRegistration, TriggerSubscription, CreateTriggerSubscriptionResponse, } from './types.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -41,6 +41,9 @@ export { OPENWOP_COST_ATTRIBUTE_NAMES, sanitizeCostAttributes, } from './cost-at
|
|
|
41
41
|
// subscription secret must never reach a browser.
|
|
42
42
|
/** @deprecated Import from `@openwop/openwop/webhooks` — server-only; removed from the barrel in the next major. */
|
|
43
43
|
export { DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS, verifyWebhookSignature, signWebhookDelivery, } from './webhook-helpers.js';
|
|
44
|
+
// RFC 0165 §C.3 — browser-safe (no Node builtin): which header family a
|
|
45
|
+
// delivery carries, and the `sha256=` / legacy `v1=` value parser.
|
|
46
|
+
export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
|
|
44
47
|
// Public-registry read helpers (SDK-5 close-out 2026-05-15). Read-only
|
|
45
48
|
// typed client for the public node-pack registry at packs.openwop.dev
|
|
46
49
|
// per spec/v1/registry-operations.md.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA8GtC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGxC,2EAA2E;AAC3E,6DAA6D;AAC7D,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC;AAM5B,yEAAyE;AACzE,yEAAyE;AACzE,oBAAoB;AACpB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,kBAAkB,CAAC;AAS1B,iDAAiD;AACjD,sEAAsE;AACtE,mEAAmE;AACnE,uDAAuD;AACvD,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAG/B,sEAAsE;AACtE,4DAA4D;AAC5D,sEAAsE;AACtE,uDAAuD;AACvD,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,0DAA0D;AAC1D,4EAA4E;AAC5E,uDAAuD;AACvD,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,0EAA0E;AAC1E,kDAAkD;AAClD,oHAAoH;AACpH,OAAO,EACL,wCAAwC,EACxC,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAM9B,uEAAuE;AACvE,sEAAsE;AACtE,sCAAsC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA0CvD,oEAAoE;AACpE,wEAAwE;AACxE,uEAAuE;AACvE,uEAAuE;AACvE,oEAAoE;AACpE,uDAAuD;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,mEAAmE;AACnE,wEAAwE;AACxE,uEAAuE;AACvE,qEAAqE;AACrE,qEAAqE;AACrE,sEAAsE;AACtE,8DAA8D;AAC9D,sEAAsE;AACtE,oDAAoD;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA8GtC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGxC,2EAA2E;AAC3E,6DAA6D;AAC7D,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC;AAM5B,yEAAyE;AACzE,yEAAyE;AACzE,oBAAoB;AACpB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,kBAAkB,CAAC;AAS1B,iDAAiD;AACjD,sEAAsE;AACtE,mEAAmE;AACnE,uDAAuD;AACvD,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAG/B,sEAAsE;AACtE,4DAA4D;AAC5D,sEAAsE;AACtE,uDAAuD;AACvD,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,0DAA0D;AAC1D,4EAA4E;AAC5E,uDAAuD;AACvD,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,0EAA0E;AAC1E,kDAAkD;AAClD,oHAAoH;AACpH,OAAO,EACL,wCAAwC,EACxC,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAM9B,wEAAwE;AACxE,mEAAmE;AACnE,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGhH,uEAAuE;AACvE,sEAAsE;AACtE,sCAAsC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA0CvD,oEAAoE;AACpE,wEAAwE;AACxE,uEAAuE;AACvE,uEAAuE;AACvE,oEAAoE;AACpE,uDAAuD;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,mEAAmE;AACnE,wEAAwE;AACxE,uEAAuE;AACvE,qEAAqE;AACrE,qEAAqE;AACrE,sEAAsE;AACtE,8DAA8D;AAC9D,sEAAsE;AACtE,oDAAoD;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook header families and signature-value parsing (RFC 0165 §C.3).
|
|
3
|
+
* Pure — no Node builtin — so both the server module (`webhook-helpers.ts`)
|
|
4
|
+
* and the browser stub re-export it. Verification itself stays server-only.
|
|
5
|
+
*/
|
|
6
|
+
/** `sha256=<hex>` or `v1=<hex>` → `<hex>`; anything else → null. */
|
|
7
|
+
export declare function parseSignatureValue(value: string): string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Header-name families a delivery may carry, in the order a receiver SHOULD
|
|
10
|
+
* prefer them (RFC 0165 §C.1): the v2-bound `OpenWOP-*` family, the v1
|
|
11
|
+
* canonical `X-openwop-*` family, then the legacy names this SDK used to
|
|
12
|
+
* document. Lookups are case-insensitive.
|
|
13
|
+
*/
|
|
14
|
+
export declare const WEBHOOK_HEADER_FAMILIES: ReadonlyArray<{
|
|
15
|
+
readonly signature: string;
|
|
16
|
+
readonly timestamp: string;
|
|
17
|
+
readonly algorithm?: string;
|
|
18
|
+
}>;
|
|
19
|
+
export interface WebhookHeaderRead {
|
|
20
|
+
readonly signatureHeader: string;
|
|
21
|
+
readonly timestampHeader: string;
|
|
22
|
+
/** Which family was read: `openwop`, `x-openwop`, or `legacy`. */
|
|
23
|
+
readonly family: 'openwop' | 'x-openwop' | 'legacy';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Pick the signature + timestamp values out of a delivery's headers, first
|
|
27
|
+
* present family wins. Returns null when no family is complete. Pass a plain
|
|
28
|
+
* object (any casing) or a `Headers`-like with a `get` method.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readWebhookHeaders(headers: Record<string, string | string[] | undefined> | {
|
|
31
|
+
get(name: string): string | null;
|
|
32
|
+
}): WebhookHeaderRead | null;
|
|
33
|
+
//# sourceMappingURL=webhook-header-families.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-header-families.d.ts","sourceRoot":"","sources":["../src/webhook-header-families.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQhE;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAI1I,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;CACrD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,GAC5F,iBAAiB,GAAG,IAAI,CAmB1B"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook header families and signature-value parsing (RFC 0165 §C.3).
|
|
3
|
+
* Pure — no Node builtin — so both the server module (`webhook-helpers.ts`)
|
|
4
|
+
* and the browser stub re-export it. Verification itself stays server-only.
|
|
5
|
+
*/
|
|
6
|
+
/** Accepted signature-value prefixes, spec form first. */
|
|
7
|
+
const SIGNATURE_VALUE_PREFIXES = ['sha256=', 'v1='];
|
|
8
|
+
/** `sha256=<hex>` or `v1=<hex>` → `<hex>`; anything else → null. */
|
|
9
|
+
export function parseSignatureValue(value) {
|
|
10
|
+
for (const p of SIGNATURE_VALUE_PREFIXES) {
|
|
11
|
+
if (value.startsWith(p)) {
|
|
12
|
+
const hex = value.slice(p.length);
|
|
13
|
+
return /^[0-9a-f]+$/i.test(hex) ? hex : null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Header-name families a delivery may carry, in the order a receiver SHOULD
|
|
20
|
+
* prefer them (RFC 0165 §C.1): the v2-bound `OpenWOP-*` family, the v1
|
|
21
|
+
* canonical `X-openwop-*` family, then the legacy names this SDK used to
|
|
22
|
+
* document. Lookups are case-insensitive.
|
|
23
|
+
*/
|
|
24
|
+
export const WEBHOOK_HEADER_FAMILIES = [
|
|
25
|
+
{ signature: 'OpenWOP-Signature', timestamp: 'OpenWOP-Timestamp', algorithm: 'OpenWOP-Signature-Algorithm' },
|
|
26
|
+
{ signature: 'X-openwop-Signature', timestamp: 'X-openwop-Timestamp', algorithm: 'X-openwop-Signature-Algorithm' },
|
|
27
|
+
{ signature: 'openwop-Webhook-Signature', timestamp: 'openwop-Webhook-Timestamp' },
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Pick the signature + timestamp values out of a delivery's headers, first
|
|
31
|
+
* present family wins. Returns null when no family is complete. Pass a plain
|
|
32
|
+
* object (any casing) or a `Headers`-like with a `get` method.
|
|
33
|
+
*/
|
|
34
|
+
export function readWebhookHeaders(headers) {
|
|
35
|
+
const get = (name) => {
|
|
36
|
+
if (typeof headers.get === 'function') {
|
|
37
|
+
const v = headers.get(name);
|
|
38
|
+
return v === null ? undefined : v;
|
|
39
|
+
}
|
|
40
|
+
const rec = headers;
|
|
41
|
+
const key = Object.keys(rec).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
42
|
+
const v = key === undefined ? undefined : rec[key];
|
|
43
|
+
return Array.isArray(v) ? v[0] : v;
|
|
44
|
+
};
|
|
45
|
+
const families = ['openwop', 'x-openwop', 'legacy'];
|
|
46
|
+
for (let i = 0; i < WEBHOOK_HEADER_FAMILIES.length; i++) {
|
|
47
|
+
const f = WEBHOOK_HEADER_FAMILIES[i];
|
|
48
|
+
const sig = get(f.signature);
|
|
49
|
+
const ts = get(f.timestamp);
|
|
50
|
+
if (sig !== undefined && ts !== undefined)
|
|
51
|
+
return { signatureHeader: sig, timestampHeader: ts, family: families[i] };
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=webhook-header-families.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-header-families.js","sourceRoot":"","sources":["../src/webhook-header-families.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,0DAA0D;AAC1D,MAAM,wBAAwB,GAAG,CAAC,SAAS,EAAE,KAAK,CAAU,CAAC;AAE7D,oEAAoE;AACpE,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,KAAK,MAAM,CAAC,IAAI,wBAAwB,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAClC,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAA2G;IAC7I,EAAE,SAAS,EAAE,mBAAmB,EAAE,SAAS,EAAE,mBAAmB,EAAE,SAAS,EAAE,6BAA6B,EAAE;IAC5G,EAAE,SAAS,EAAE,qBAAqB,EAAE,SAAS,EAAE,qBAAqB,EAAE,SAAS,EAAE,+BAA+B,EAAE;IAClH,EAAE,SAAS,EAAE,2BAA2B,EAAE,SAAS,EAAE,2BAA2B,EAAE;CACnF,CAAC;AASF;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAA6F;IAE7F,MAAM,GAAG,GAAG,CAAC,IAAY,EAAsB,EAAE;QAC/C,IAAI,OAAQ,OAA6B,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;YAC7D,MAAM,CAAC,GAAI,OAAgD,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACtE,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,GAAG,GAAG,OAAwD,CAAC;QACrE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACjF,MAAM,CAAC,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAU,CAAC;IAC7D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxD,MAAM,CAAC,GAAG,uBAAuB,CAAC,CAAC,CAAE,CAAC;QACtC,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC5B,IAAI,GAAG,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;YAAE,OAAO,EAAE,eAAe,EAAE,GAAG,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAE,EAAE,CAAC;IACxH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -33,5 +33,6 @@
|
|
|
33
33
|
export declare const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
|
|
34
34
|
export declare function verifyWebhookSignature(): never;
|
|
35
35
|
export declare function signWebhookDelivery(): never;
|
|
36
|
-
export
|
|
36
|
+
export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
|
|
37
|
+
export type { VerifyWebhookSignatureOptions, VerifyWebhookOutcome, SignedWebhookDelivery, WebhookHeaderRead } from './webhook-helpers.js';
|
|
37
38
|
//# sourceMappingURL=webhook-helpers.browser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-helpers.browser.d.ts","sourceRoot":"","sources":["../src/webhook-helpers.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAOH,2DAA2D;AAC3D,eAAO,MAAM,wCAAwC,MAAM,CAAC;AAE5D,wBAAgB,sBAAsB,IAAI,KAAK,CAE9C;AAED,wBAAgB,mBAAmB,IAAI,KAAK,CAE3C;
|
|
1
|
+
{"version":3,"file":"webhook-helpers.browser.d.ts","sourceRoot":"","sources":["../src/webhook-helpers.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAOH,2DAA2D;AAC3D,eAAO,MAAM,wCAAwC,MAAM,CAAC;AAE5D,wBAAgB,sBAAsB,IAAI,KAAK,CAE9C;AAED,wBAAgB,mBAAmB,IAAI,KAAK,CAE3C;AAKD,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEhH,YAAY,EAAE,6BAA6B,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -40,4 +40,8 @@ export function verifyWebhookSignature() {
|
|
|
40
40
|
export function signWebhookDelivery() {
|
|
41
41
|
throw new Error(REASON);
|
|
42
42
|
}
|
|
43
|
+
// RFC 0165 §C.3 — the header-family readers need no Node builtin, so the
|
|
44
|
+
// browser build carries the real implementations (a browser MAY inspect which
|
|
45
|
+
// family a delivery carries; it still MUST NOT verify — no secret in a browser).
|
|
46
|
+
export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
|
|
43
47
|
//# sourceMappingURL=webhook-helpers.browser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-helpers.browser.js","sourceRoot":"","sources":["../src/webhook-helpers.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,MAAM,GACV,sIAAsI;MACpI,sGAAsG;MACtG,6DAA6D,CAAC;AAElE,2DAA2D;AAC3D,MAAM,CAAC,MAAM,wCAAwC,GAAG,GAAG,CAAC;AAE5D,MAAM,UAAU,sBAAsB;IACpC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC"}
|
|
1
|
+
{"version":3,"file":"webhook-helpers.browser.js","sourceRoot":"","sources":["../src/webhook-helpers.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,MAAM,GACV,sIAAsI;MACpI,sGAAsG;MACtG,6DAA6D,CAAC;AAElE,2DAA2D;AAC3D,MAAM,CAAC,MAAM,wCAAwC,GAAG,GAAG,CAAC;AAE5D,MAAM,UAAU,sBAAsB;IACpC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC;AAED,yEAAyE;AACzE,8EAA8E;AAC9E,iFAAiF;AACjF,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -4,15 +4,23 @@
|
|
|
4
4
|
* timestamp freshness before accepting a delivery — verifying HMAC
|
|
5
5
|
* alone leaves the receiver open to replay attacks.
|
|
6
6
|
*
|
|
7
|
-
* The canonical signing recipe:
|
|
7
|
+
* The canonical signing recipe (`webhooks.md` §"Headers"):
|
|
8
8
|
*
|
|
9
9
|
* hmac = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
|
|
10
|
-
* header `openwop-
|
|
11
|
-
* header `
|
|
10
|
+
* header `X-openwop-Signature: sha256=<hmac-hex>` (v1 canonical)
|
|
11
|
+
* header `OpenWOP-Signature: sha256=<hmac-hex>` (RFC 0165 §C.1, dual-emitted)
|
|
12
|
+
* header `X-openwop-Timestamp` / `OpenWOP-Timestamp: <unix-seconds>`
|
|
13
|
+
*
|
|
14
|
+
* History (RFC 0165 §C.3): until 1.9.0 this helper read a header named
|
|
15
|
+
* `openwop-Webhook-Signature` carrying `v1=<hex>` — a name and value shape
|
|
16
|
+
* that appear in no spec file. A spec-conformant `sha256=` delivery failed
|
|
17
|
+
* verification outright. The helper now accepts BOTH value forms, and
|
|
18
|
+
* `readWebhookHeaders` picks the first present header family in spec order
|
|
19
|
+
* (`OpenWOP-*`, then `X-openwop-*`, then the legacy `openwop-Webhook-*`).
|
|
12
20
|
*
|
|
13
21
|
* Verification:
|
|
14
22
|
*
|
|
15
|
-
* 1. Parse the `v1=<hex>` value from the signature header.
|
|
23
|
+
* 1. Parse the `sha256=<hex>` (or legacy `v1=<hex>`) value from the signature header.
|
|
16
24
|
* 2. Recompute `expected = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)`.
|
|
17
25
|
* 3. Compare using **constant-time** equality (timing-safe).
|
|
18
26
|
* 4. Reject when `|now - timestamp|` exceeds the freshness window
|
|
@@ -55,19 +63,27 @@ export type VerifyWebhookOutcome = {
|
|
|
55
63
|
* signs the exact bytes it delivered.
|
|
56
64
|
*
|
|
57
65
|
* @param secret The pre-shared secret returned from `webhooks.register`.
|
|
58
|
-
* @param signatureHeader The value of the `openwop-Webhook-Signature`
|
|
59
|
-
* @param timestampHeader The value of the
|
|
66
|
+
* @param signatureHeader The value of the signature header — `OpenWOP-Signature` / `X-openwop-Signature` (`"sha256=abc123…"`) or the legacy `openwop-Webhook-Signature` (`"v1=abc123…"`); see `readWebhookHeaders`.
|
|
67
|
+
* @param timestampHeader The value of the matching timestamp header (unix seconds as string).
|
|
60
68
|
* @param rawBody The exact request body bytes the host POSTed.
|
|
61
69
|
*/
|
|
62
70
|
export declare function verifyWebhookSignature(secret: string, signatureHeader: string, timestampHeader: string, rawBody: string | Buffer, options?: VerifyWebhookSignatureOptions): VerifyWebhookOutcome;
|
|
71
|
+
export interface SignedWebhookDelivery {
|
|
72
|
+
/** Spec form: `sha256=<hex>` (webhooks.md §"Headers"). */
|
|
73
|
+
readonly signatureHeader: string;
|
|
74
|
+
/** Legacy form this SDK used to emit: `v1=<hex>` (RFC 0165 §C.3). */
|
|
75
|
+
readonly legacySignatureHeader: string;
|
|
76
|
+
readonly timestampHeader: string;
|
|
77
|
+
/** Every header a host should send during the RFC 0165 overlap, by exact name. */
|
|
78
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
79
|
+
}
|
|
63
80
|
/**
|
|
64
81
|
* Compute the canonical webhook signature for a payload — useful when
|
|
65
82
|
* implementing a host (forward direction) OR when generating test
|
|
66
83
|
* fixtures. Receivers verify via `verifyWebhookSignature`; this is the
|
|
67
84
|
* inverse.
|
|
68
85
|
*/
|
|
69
|
-
export declare function signWebhookDelivery(secret: string, timestamp: number, rawBody: string | Buffer):
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
86
|
+
export declare function signWebhookDelivery(secret: string, timestamp: number, rawBody: string | Buffer): SignedWebhookDelivery;
|
|
87
|
+
export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
|
|
88
|
+
export type { WebhookHeaderRead } from './webhook-header-families.js';
|
|
73
89
|
//# sourceMappingURL=webhook-helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-helpers.d.ts","sourceRoot":"","sources":["../src/webhook-helpers.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"webhook-helpers.d.ts","sourceRoot":"","sources":["../src/webhook-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH,sFAAsF;AACtF,eAAO,MAAM,wCAAwC,MAAM,CAAC;AAE5D,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,GAAG,mBAAmB,GAAG,6BAA6B,GAAG,4BAA4B,GAAG,4BAA4B,CAAA;CAAE,CAAC;AAEvK;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,EACvB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,OAAO,GAAE,6BAAkC,GAC1C,oBAAoB,CA0CtB;AAED,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,qEAAqE;IACrE,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAAG,MAAM,GACvB,qBAAqB,CAkBvB;AAED,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAChH,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC"}
|
package/dist/webhook-helpers.js
CHANGED
|
@@ -4,15 +4,23 @@
|
|
|
4
4
|
* timestamp freshness before accepting a delivery — verifying HMAC
|
|
5
5
|
* alone leaves the receiver open to replay attacks.
|
|
6
6
|
*
|
|
7
|
-
* The canonical signing recipe:
|
|
7
|
+
* The canonical signing recipe (`webhooks.md` §"Headers"):
|
|
8
8
|
*
|
|
9
9
|
* hmac = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
|
|
10
|
-
* header `openwop-
|
|
11
|
-
* header `
|
|
10
|
+
* header `X-openwop-Signature: sha256=<hmac-hex>` (v1 canonical)
|
|
11
|
+
* header `OpenWOP-Signature: sha256=<hmac-hex>` (RFC 0165 §C.1, dual-emitted)
|
|
12
|
+
* header `X-openwop-Timestamp` / `OpenWOP-Timestamp: <unix-seconds>`
|
|
13
|
+
*
|
|
14
|
+
* History (RFC 0165 §C.3): until 1.9.0 this helper read a header named
|
|
15
|
+
* `openwop-Webhook-Signature` carrying `v1=<hex>` — a name and value shape
|
|
16
|
+
* that appear in no spec file. A spec-conformant `sha256=` delivery failed
|
|
17
|
+
* verification outright. The helper now accepts BOTH value forms, and
|
|
18
|
+
* `readWebhookHeaders` picks the first present header family in spec order
|
|
19
|
+
* (`OpenWOP-*`, then `X-openwop-*`, then the legacy `openwop-Webhook-*`).
|
|
12
20
|
*
|
|
13
21
|
* Verification:
|
|
14
22
|
*
|
|
15
|
-
* 1. Parse the `v1=<hex>` value from the signature header.
|
|
23
|
+
* 1. Parse the `sha256=<hex>` (or legacy `v1=<hex>`) value from the signature header.
|
|
16
24
|
* 2. Recompute `expected = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)`.
|
|
17
25
|
* 3. Compare using **constant-time** equality (timing-safe).
|
|
18
26
|
* 4. Reject when `|now - timestamp|` exceeds the freshness window
|
|
@@ -25,6 +33,7 @@
|
|
|
25
33
|
* @module @openwop/openwop/webhook-helpers
|
|
26
34
|
*/
|
|
27
35
|
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
36
|
+
import { parseSignatureValue } from './webhook-header-families.js';
|
|
28
37
|
/** Default freshness window per `spec/v1/webhooks.md` §"Replay attack resistance". */
|
|
29
38
|
export const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
|
|
30
39
|
/**
|
|
@@ -37,16 +46,17 @@ export const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
|
|
|
37
46
|
* signs the exact bytes it delivered.
|
|
38
47
|
*
|
|
39
48
|
* @param secret The pre-shared secret returned from `webhooks.register`.
|
|
40
|
-
* @param signatureHeader The value of the `openwop-Webhook-Signature`
|
|
41
|
-
* @param timestampHeader The value of the
|
|
49
|
+
* @param signatureHeader The value of the signature header — `OpenWOP-Signature` / `X-openwop-Signature` (`"sha256=abc123…"`) or the legacy `openwop-Webhook-Signature` (`"v1=abc123…"`); see `readWebhookHeaders`.
|
|
50
|
+
* @param timestampHeader The value of the matching timestamp header (unix seconds as string).
|
|
42
51
|
* @param rawBody The exact request body bytes the host POSTed.
|
|
43
52
|
*/
|
|
44
53
|
export function verifyWebhookSignature(secret, signatureHeader, timestampHeader, rawBody, options = {}) {
|
|
45
|
-
// 1. Parse the signature header.
|
|
46
|
-
|
|
54
|
+
// 1. Parse the signature header — spec form `sha256=<hex>` (webhooks.md
|
|
55
|
+
// §"Headers") or the legacy SDK form `v1=<hex>` (RFC 0165 §C.3).
|
|
56
|
+
const providedHex = parseSignatureValue(signatureHeader);
|
|
57
|
+
if (providedHex === null) {
|
|
47
58
|
return { valid: false, reason: 'malformed_signature_header' };
|
|
48
59
|
}
|
|
49
|
-
const providedHex = signatureHeader.slice(3);
|
|
50
60
|
if (!/^[0-9a-f]+$/i.test(providedHex)) {
|
|
51
61
|
return { valid: false, reason: 'malformed_signature_header' };
|
|
52
62
|
}
|
|
@@ -90,8 +100,20 @@ export function signWebhookDelivery(secret, timestamp, rawBody) {
|
|
|
90
100
|
const bodyStr = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8');
|
|
91
101
|
const hex = createHmac('sha256', secret).update(`${timestamp}.${bodyStr}`, 'utf8').digest('hex');
|
|
92
102
|
return {
|
|
93
|
-
signatureHeader: `
|
|
103
|
+
signatureHeader: `sha256=${hex}`,
|
|
104
|
+
legacySignatureHeader: `v1=${hex}`,
|
|
94
105
|
timestampHeader: String(timestamp),
|
|
106
|
+
headers: {
|
|
107
|
+
'OpenWOP-Signature': `sha256=${hex}`,
|
|
108
|
+
'OpenWOP-Timestamp': String(timestamp),
|
|
109
|
+
'OpenWOP-Signature-Algorithm': 'v1',
|
|
110
|
+
'X-openwop-Signature': `sha256=${hex}`,
|
|
111
|
+
'X-openwop-Timestamp': String(timestamp),
|
|
112
|
+
'X-openwop-Signature-Algorithm': 'v1',
|
|
113
|
+
'openwop-Webhook-Signature': `v1=${hex}`,
|
|
114
|
+
'openwop-Webhook-Timestamp': String(timestamp),
|
|
115
|
+
},
|
|
95
116
|
};
|
|
96
117
|
}
|
|
118
|
+
export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
|
|
97
119
|
//# sourceMappingURL=webhook-helpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-helpers.js","sourceRoot":"","sources":["../src/webhook-helpers.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"webhook-helpers.js","sourceRoot":"","sources":["../src/webhook-helpers.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -208,7 +208,12 @@ export {
|
|
|
208
208
|
export type {
|
|
209
209
|
VerifyWebhookSignatureOptions,
|
|
210
210
|
VerifyWebhookOutcome,
|
|
211
|
+
SignedWebhookDelivery,
|
|
211
212
|
} from './webhook-helpers.js';
|
|
213
|
+
// RFC 0165 §C.3 — browser-safe (no Node builtin): which header family a
|
|
214
|
+
// delivery carries, and the `sha256=` / legacy `v1=` value parser.
|
|
215
|
+
export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
|
|
216
|
+
export type { WebhookHeaderRead } from './webhook-header-families.js';
|
|
212
217
|
|
|
213
218
|
// Public-registry read helpers (SDK-5 close-out 2026-05-15). Read-only
|
|
214
219
|
// typed client for the public node-pack registry at packs.openwop.dev
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook header families and signature-value parsing (RFC 0165 §C.3).
|
|
3
|
+
* Pure — no Node builtin — so both the server module (`webhook-helpers.ts`)
|
|
4
|
+
* and the browser stub re-export it. Verification itself stays server-only.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Accepted signature-value prefixes, spec form first. */
|
|
8
|
+
const SIGNATURE_VALUE_PREFIXES = ['sha256=', 'v1='] as const;
|
|
9
|
+
|
|
10
|
+
/** `sha256=<hex>` or `v1=<hex>` → `<hex>`; anything else → null. */
|
|
11
|
+
export function parseSignatureValue(value: string): string | null {
|
|
12
|
+
for (const p of SIGNATURE_VALUE_PREFIXES) {
|
|
13
|
+
if (value.startsWith(p)) {
|
|
14
|
+
const hex = value.slice(p.length);
|
|
15
|
+
return /^[0-9a-f]+$/i.test(hex) ? hex : null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Header-name families a delivery may carry, in the order a receiver SHOULD
|
|
23
|
+
* prefer them (RFC 0165 §C.1): the v2-bound `OpenWOP-*` family, the v1
|
|
24
|
+
* canonical `X-openwop-*` family, then the legacy names this SDK used to
|
|
25
|
+
* document. Lookups are case-insensitive.
|
|
26
|
+
*/
|
|
27
|
+
export const WEBHOOK_HEADER_FAMILIES: ReadonlyArray<{ readonly signature: string; readonly timestamp: string; readonly algorithm?: string }> = [
|
|
28
|
+
{ signature: 'OpenWOP-Signature', timestamp: 'OpenWOP-Timestamp', algorithm: 'OpenWOP-Signature-Algorithm' },
|
|
29
|
+
{ signature: 'X-openwop-Signature', timestamp: 'X-openwop-Timestamp', algorithm: 'X-openwop-Signature-Algorithm' },
|
|
30
|
+
{ signature: 'openwop-Webhook-Signature', timestamp: 'openwop-Webhook-Timestamp' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export interface WebhookHeaderRead {
|
|
34
|
+
readonly signatureHeader: string;
|
|
35
|
+
readonly timestampHeader: string;
|
|
36
|
+
/** Which family was read: `openwop`, `x-openwop`, or `legacy`. */
|
|
37
|
+
readonly family: 'openwop' | 'x-openwop' | 'legacy';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Pick the signature + timestamp values out of a delivery's headers, first
|
|
42
|
+
* present family wins. Returns null when no family is complete. Pass a plain
|
|
43
|
+
* object (any casing) or a `Headers`-like with a `get` method.
|
|
44
|
+
*/
|
|
45
|
+
export function readWebhookHeaders(
|
|
46
|
+
headers: Record<string, string | string[] | undefined> | { get(name: string): string | null },
|
|
47
|
+
): WebhookHeaderRead | null {
|
|
48
|
+
const get = (name: string): string | undefined => {
|
|
49
|
+
if (typeof (headers as { get?: unknown }).get === 'function') {
|
|
50
|
+
const v = (headers as { get(name: string): string | null }).get(name);
|
|
51
|
+
return v === null ? undefined : v;
|
|
52
|
+
}
|
|
53
|
+
const rec = headers as Record<string, string | string[] | undefined>;
|
|
54
|
+
const key = Object.keys(rec).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
55
|
+
const v = key === undefined ? undefined : rec[key];
|
|
56
|
+
return Array.isArray(v) ? v[0] : v;
|
|
57
|
+
};
|
|
58
|
+
const families = ['openwop', 'x-openwop', 'legacy'] as const;
|
|
59
|
+
for (let i = 0; i < WEBHOOK_HEADER_FAMILIES.length; i++) {
|
|
60
|
+
const f = WEBHOOK_HEADER_FAMILIES[i]!;
|
|
61
|
+
const sig = get(f.signature);
|
|
62
|
+
const ts = get(f.timestamp);
|
|
63
|
+
if (sig !== undefined && ts !== undefined) return { signatureHeader: sig, timestampHeader: ts, family: families[i]! };
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
@@ -46,4 +46,9 @@ export function signWebhookDelivery(): never {
|
|
|
46
46
|
throw new Error(REASON);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
// RFC 0165 §C.3 — the header-family readers need no Node builtin, so the
|
|
50
|
+
// browser build carries the real implementations (a browser MAY inspect which
|
|
51
|
+
// family a delivery carries; it still MUST NOT verify — no secret in a browser).
|
|
52
|
+
export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
|
|
53
|
+
|
|
54
|
+
export type { VerifyWebhookSignatureOptions, VerifyWebhookOutcome, SignedWebhookDelivery, WebhookHeaderRead } from './webhook-helpers.js';
|
package/src/webhook-helpers.ts
CHANGED
|
@@ -4,15 +4,23 @@
|
|
|
4
4
|
* timestamp freshness before accepting a delivery — verifying HMAC
|
|
5
5
|
* alone leaves the receiver open to replay attacks.
|
|
6
6
|
*
|
|
7
|
-
* The canonical signing recipe:
|
|
7
|
+
* The canonical signing recipe (`webhooks.md` §"Headers"):
|
|
8
8
|
*
|
|
9
9
|
* hmac = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
|
|
10
|
-
* header `openwop-
|
|
11
|
-
* header `
|
|
10
|
+
* header `X-openwop-Signature: sha256=<hmac-hex>` (v1 canonical)
|
|
11
|
+
* header `OpenWOP-Signature: sha256=<hmac-hex>` (RFC 0165 §C.1, dual-emitted)
|
|
12
|
+
* header `X-openwop-Timestamp` / `OpenWOP-Timestamp: <unix-seconds>`
|
|
13
|
+
*
|
|
14
|
+
* History (RFC 0165 §C.3): until 1.9.0 this helper read a header named
|
|
15
|
+
* `openwop-Webhook-Signature` carrying `v1=<hex>` — a name and value shape
|
|
16
|
+
* that appear in no spec file. A spec-conformant `sha256=` delivery failed
|
|
17
|
+
* verification outright. The helper now accepts BOTH value forms, and
|
|
18
|
+
* `readWebhookHeaders` picks the first present header family in spec order
|
|
19
|
+
* (`OpenWOP-*`, then `X-openwop-*`, then the legacy `openwop-Webhook-*`).
|
|
12
20
|
*
|
|
13
21
|
* Verification:
|
|
14
22
|
*
|
|
15
|
-
* 1. Parse the `v1=<hex>` value from the signature header.
|
|
23
|
+
* 1. Parse the `sha256=<hex>` (or legacy `v1=<hex>`) value from the signature header.
|
|
16
24
|
* 2. Recompute `expected = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)`.
|
|
17
25
|
* 3. Compare using **constant-time** equality (timing-safe).
|
|
18
26
|
* 4. Reject when `|now - timestamp|` exceeds the freshness window
|
|
@@ -26,6 +34,7 @@
|
|
|
26
34
|
*/
|
|
27
35
|
|
|
28
36
|
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
37
|
+
import { parseSignatureValue } from './webhook-header-families.js';
|
|
29
38
|
|
|
30
39
|
/** Default freshness window per `spec/v1/webhooks.md` §"Replay attack resistance". */
|
|
31
40
|
export const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
|
|
@@ -58,8 +67,8 @@ export type VerifyWebhookOutcome =
|
|
|
58
67
|
* signs the exact bytes it delivered.
|
|
59
68
|
*
|
|
60
69
|
* @param secret The pre-shared secret returned from `webhooks.register`.
|
|
61
|
-
* @param signatureHeader The value of the `openwop-Webhook-Signature`
|
|
62
|
-
* @param timestampHeader The value of the
|
|
70
|
+
* @param signatureHeader The value of the signature header — `OpenWOP-Signature` / `X-openwop-Signature` (`"sha256=abc123…"`) or the legacy `openwop-Webhook-Signature` (`"v1=abc123…"`); see `readWebhookHeaders`.
|
|
71
|
+
* @param timestampHeader The value of the matching timestamp header (unix seconds as string).
|
|
63
72
|
* @param rawBody The exact request body bytes the host POSTed.
|
|
64
73
|
*/
|
|
65
74
|
export function verifyWebhookSignature(
|
|
@@ -69,11 +78,12 @@ export function verifyWebhookSignature(
|
|
|
69
78
|
rawBody: string | Buffer,
|
|
70
79
|
options: VerifyWebhookSignatureOptions = {},
|
|
71
80
|
): VerifyWebhookOutcome {
|
|
72
|
-
// 1. Parse the signature header.
|
|
73
|
-
|
|
81
|
+
// 1. Parse the signature header — spec form `sha256=<hex>` (webhooks.md
|
|
82
|
+
// §"Headers") or the legacy SDK form `v1=<hex>` (RFC 0165 §C.3).
|
|
83
|
+
const providedHex = parseSignatureValue(signatureHeader);
|
|
84
|
+
if (providedHex === null) {
|
|
74
85
|
return { valid: false, reason: 'malformed_signature_header' };
|
|
75
86
|
}
|
|
76
|
-
const providedHex = signatureHeader.slice(3);
|
|
77
87
|
if (!/^[0-9a-f]+$/i.test(providedHex)) {
|
|
78
88
|
return { valid: false, reason: 'malformed_signature_header' };
|
|
79
89
|
}
|
|
@@ -111,6 +121,16 @@ export function verifyWebhookSignature(
|
|
|
111
121
|
return { valid: true };
|
|
112
122
|
}
|
|
113
123
|
|
|
124
|
+
export interface SignedWebhookDelivery {
|
|
125
|
+
/** Spec form: `sha256=<hex>` (webhooks.md §"Headers"). */
|
|
126
|
+
readonly signatureHeader: string;
|
|
127
|
+
/** Legacy form this SDK used to emit: `v1=<hex>` (RFC 0165 §C.3). */
|
|
128
|
+
readonly legacySignatureHeader: string;
|
|
129
|
+
readonly timestampHeader: string;
|
|
130
|
+
/** Every header a host should send during the RFC 0165 overlap, by exact name. */
|
|
131
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
132
|
+
}
|
|
133
|
+
|
|
114
134
|
/**
|
|
115
135
|
* Compute the canonical webhook signature for a payload — useful when
|
|
116
136
|
* implementing a host (forward direction) OR when generating test
|
|
@@ -121,11 +141,25 @@ export function signWebhookDelivery(
|
|
|
121
141
|
secret: string,
|
|
122
142
|
timestamp: number,
|
|
123
143
|
rawBody: string | Buffer,
|
|
124
|
-
):
|
|
144
|
+
): SignedWebhookDelivery {
|
|
125
145
|
const bodyStr = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8');
|
|
126
146
|
const hex = createHmac('sha256', secret).update(`${timestamp}.${bodyStr}`, 'utf8').digest('hex');
|
|
127
147
|
return {
|
|
128
|
-
signatureHeader: `
|
|
148
|
+
signatureHeader: `sha256=${hex}`,
|
|
149
|
+
legacySignatureHeader: `v1=${hex}`,
|
|
129
150
|
timestampHeader: String(timestamp),
|
|
151
|
+
headers: {
|
|
152
|
+
'OpenWOP-Signature': `sha256=${hex}`,
|
|
153
|
+
'OpenWOP-Timestamp': String(timestamp),
|
|
154
|
+
'OpenWOP-Signature-Algorithm': 'v1',
|
|
155
|
+
'X-openwop-Signature': `sha256=${hex}`,
|
|
156
|
+
'X-openwop-Timestamp': String(timestamp),
|
|
157
|
+
'X-openwop-Signature-Algorithm': 'v1',
|
|
158
|
+
'openwop-Webhook-Signature': `v1=${hex}`,
|
|
159
|
+
'openwop-Webhook-Timestamp': String(timestamp),
|
|
160
|
+
},
|
|
130
161
|
};
|
|
131
162
|
}
|
|
163
|
+
|
|
164
|
+
export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
|
|
165
|
+
export type { WebhookHeaderRead } from './webhook-header-families.js';
|