@openwop/openwop 1.9.0 → 2.0.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/README.md +64 -117
- package/dist/client.d.ts +131 -245
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +233 -440
- package/dist/client.js.map +1 -1
- package/dist/cost-attribution.d.ts +2 -2
- package/dist/cost-attribution.js +2 -2
- package/dist/envelope-directive.d.ts +1 -1
- package/dist/envelope-directive.js +1 -1
- package/dist/event-helpers.js +1 -1
- package/dist/event-helpers.js.map +1 -1
- package/dist/generated.d.ts +17 -0
- package/dist/generated.d.ts.map +1 -0
- package/dist/generated.js +311 -0
- package/dist/generated.js.map +1 -0
- package/dist/index.d.ts +16 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -56
- package/dist/index.js.map +1 -1
- package/dist/run-helpers.d.ts +20 -21
- package/dist/run-helpers.d.ts.map +1 -1
- package/dist/run-helpers.js +23 -72
- package/dist/run-helpers.js.map +1 -1
- package/dist/sse.d.ts +33 -15
- package/dist/sse.d.ts.map +1 -1
- package/dist/sse.js +28 -30
- package/dist/sse.js.map +1 -1
- package/dist/types.d.ts +253 -559
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/webhook-header-families.d.ts +22 -13
- package/dist/webhook-header-families.d.ts.map +1 -1
- package/dist/webhook-header-families.js +30 -27
- package/dist/webhook-header-families.js.map +1 -1
- package/dist/webhook-helpers.browser.d.ts +15 -29
- package/dist/webhook-helpers.browser.d.ts.map +1 -1
- package/dist/webhook-helpers.browser.js +16 -31
- package/dist/webhook-helpers.browser.js.map +1 -1
- package/dist/webhook-helpers.d.ts +41 -41
- package/dist/webhook-helpers.d.ts.map +1 -1
- package/dist/webhook-helpers.js +40 -47
- package/dist/webhook-helpers.js.map +1 -1
- package/package.json +6 -4
- package/src/client.ts +255 -454
- package/src/cost-attribution.ts +2 -2
- package/src/envelope-directive.ts +1 -1
- package/src/event-helpers.ts +1 -1
- package/src/generated.ts +322 -0
- package/src/index.ts +78 -110
- package/src/run-helpers.ts +27 -85
- package/src/sse.ts +63 -42
- package/src/types.ts +268 -603
- package/src/webhook-header-families.ts +41 -29
- package/src/webhook-helpers.browser.ts +23 -32
- package/src/webhook-helpers.ts +67 -53
- package/dist/registry-helpers.d.ts +0 -118
- package/dist/registry-helpers.d.ts.map +0 -1
- package/dist/registry-helpers.js +0 -82
- package/dist/registry-helpers.js.map +0 -1
- package/src/registry-helpers.ts +0 -173
|
@@ -1,46 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Webhook header families and signature-value parsing (
|
|
3
|
-
* Pure — no Node builtin — so both the server
|
|
4
|
-
* and the browser stub re-export it.
|
|
2
|
+
* Webhook header families and signature-value parsing (webhooks.md §Headers;
|
|
3
|
+
* RFC 0165 §C.1, RFC 0176 §D.2). Pure — no Node builtin — so both the server
|
|
4
|
+
* module (`webhook-helpers.ts`) and the browser stub re-export it.
|
|
5
|
+
* Verification itself stays server-only.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
/**
|
|
8
|
-
const
|
|
8
|
+
/** The one accepted signature-value form: `sha256=<hex>`. The SDK-only `v1=` form was removed in v2 (headers.md §Removed). */
|
|
9
|
+
const SIGNATURE_VALUE_PREFIX = 'sha256=';
|
|
9
10
|
|
|
10
|
-
/**
|
|
11
|
+
/** The one signature scheme (`OpenWOP-Signature-Algorithm`): HMAC-SHA256 with the subscription secret. */
|
|
12
|
+
export const WEBHOOK_SIGNATURE_ALGORITHMS = ['v1'] as const;
|
|
13
|
+
export type WebhookSignatureAlgorithm = (typeof WEBHOOK_SIGNATURE_ALGORITHMS)[number];
|
|
14
|
+
|
|
15
|
+
/** `sha256=<hex>` → `<hex>`; anything else → null. */
|
|
11
16
|
export function parseSignatureValue(value: string): string | null {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return /^[0-9a-f]+$/i.test(hex) ? hex : null;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return null;
|
|
17
|
+
if (!value.startsWith(SIGNATURE_VALUE_PREFIX)) return null;
|
|
18
|
+
const hex = value.slice(SIGNATURE_VALUE_PREFIX.length);
|
|
19
|
+
return /^[0-9a-f]+$/i.test(hex) ? hex : null;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Header-name families a delivery may carry, in the order a receiver SHOULD
|
|
23
|
-
* prefer them
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* prefer them: the v2 `OpenWOP-*` family, then the v1 `X-openwop-*` family a
|
|
25
|
+
* dual-major host emits beside it through the overlap (webhooks.md §Dual
|
|
26
|
+
* emission — a v2 receiver MUST accept a delivery carrying only that family).
|
|
27
|
+
* Lookups are case-insensitive.
|
|
26
28
|
*/
|
|
27
|
-
export const WEBHOOK_HEADER_FAMILIES: ReadonlyArray<{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
export const WEBHOOK_HEADER_FAMILIES: ReadonlyArray<{
|
|
30
|
+
readonly family: 'openwop' | 'x-openwop';
|
|
31
|
+
readonly signature: string;
|
|
32
|
+
readonly timestamp: string;
|
|
33
|
+
readonly algorithm: string;
|
|
34
|
+
}> = [
|
|
35
|
+
{ family: 'openwop', signature: 'OpenWOP-Signature', timestamp: 'OpenWOP-Timestamp', algorithm: 'OpenWOP-Signature-Algorithm' },
|
|
36
|
+
{ family: 'x-openwop', signature: 'X-openwop-Signature', timestamp: 'X-openwop-Timestamp', algorithm: 'X-openwop-Signature-Algorithm' },
|
|
31
37
|
];
|
|
32
38
|
|
|
33
39
|
export interface WebhookHeaderRead {
|
|
34
40
|
readonly signatureHeader: string;
|
|
35
41
|
readonly timestampHeader: string;
|
|
36
|
-
/**
|
|
37
|
-
readonly
|
|
42
|
+
/** The `*-Signature-Algorithm` value when the delivery carried one. */
|
|
43
|
+
readonly algorithmHeader?: string;
|
|
44
|
+
/** Which family was read. */
|
|
45
|
+
readonly family: 'openwop' | 'x-openwop';
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
/**
|
|
41
|
-
* Pick the signature + timestamp values out of a delivery's
|
|
42
|
-
* present family wins. Returns null when no family is
|
|
43
|
-
* object (any casing) or a `Headers`-like with a `get`
|
|
49
|
+
* Pick the signature + timestamp (+ algorithm) values out of a delivery's
|
|
50
|
+
* headers, first present family wins. Returns null when no family is
|
|
51
|
+
* complete. Pass a plain object (any casing) or a `Headers`-like with a `get`
|
|
52
|
+
* method.
|
|
44
53
|
*/
|
|
45
54
|
export function readWebhookHeaders(
|
|
46
55
|
headers: Record<string, string | string[] | undefined> | { get(name: string): string | null },
|
|
@@ -55,12 +64,15 @@ export function readWebhookHeaders(
|
|
|
55
64
|
const v = key === undefined ? undefined : rec[key];
|
|
56
65
|
return Array.isArray(v) ? v[0] : v;
|
|
57
66
|
};
|
|
58
|
-
const
|
|
59
|
-
for (let i = 0; i < WEBHOOK_HEADER_FAMILIES.length; i++) {
|
|
60
|
-
const f = WEBHOOK_HEADER_FAMILIES[i]!;
|
|
67
|
+
for (const f of WEBHOOK_HEADER_FAMILIES) {
|
|
61
68
|
const sig = get(f.signature);
|
|
62
69
|
const ts = get(f.timestamp);
|
|
63
|
-
if (sig !== undefined && ts !== undefined)
|
|
70
|
+
if (sig !== undefined && ts !== undefined) {
|
|
71
|
+
const alg = get(f.algorithm);
|
|
72
|
+
return alg === undefined
|
|
73
|
+
? { signatureHeader: sig, timestampHeader: ts, family: f.family }
|
|
74
|
+
: { signatureHeader: sig, timestampHeader: ts, algorithmHeader: alg, family: f.family };
|
|
75
|
+
}
|
|
64
76
|
}
|
|
65
77
|
return null;
|
|
66
78
|
}
|
|
@@ -1,33 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser substitute for `webhook-helpers.ts
|
|
3
|
-
*
|
|
4
|
-
* ## Why this file exists
|
|
2
|
+
* Browser substitute for `webhook-helpers.ts`.
|
|
5
3
|
*
|
|
6
4
|
* `webhook-helpers.ts` imports `node:crypto` for `createHmac` and
|
|
7
|
-
* `timingSafeEqual`. The
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* "createHmac" is not exported by "__vite-browser-external"
|
|
13
|
-
*
|
|
14
|
-
* which names a bundler-internal shim rather than the real cause, so the error
|
|
15
|
-
* points nowhere useful. Reported 2026-05-26 and still reproducible against the
|
|
16
|
-
* published 1.7.0 fifteen months later.
|
|
17
|
-
*
|
|
18
|
-
* The `browser` field in package.json maps the Node module to this one, so the
|
|
19
|
-
* barrel is importable in a browser again. Webhook signature verification is a
|
|
20
|
-
* SERVER concern — a browser has no business holding the subscription secret —
|
|
21
|
-
* so the honest browser behaviour is to keep the import working and refuse the
|
|
22
|
-
* call, not to ship a second crypto implementation.
|
|
23
|
-
*
|
|
24
|
-
* ## Why it throws rather than returning a failure
|
|
5
|
+
* `timingSafeEqual`. The `browser` field in package.json maps that module to
|
|
6
|
+
* this one so a browser bundle builds. Webhook signature verification is a
|
|
7
|
+
* SERVER concern — a browser has no business holding the subscription
|
|
8
|
+
* secret — so the honest browser behaviour is to keep the import working and
|
|
9
|
+
* refuse the call, not to ship a second crypto implementation.
|
|
25
10
|
*
|
|
26
|
-
*
|
|
27
|
-
* silent security downgrade
|
|
28
|
-
* delivery" behaves identically whether the signature was forged
|
|
29
|
-
* platform simply could not check it.
|
|
30
|
-
*
|
|
11
|
+
* It throws rather than returning a failure: `{ valid: false }` in a browser
|
|
12
|
+
* would be a silent security downgrade — a caller that treats "not valid" as
|
|
13
|
+
* "reject the delivery" behaves identically whether the signature was forged
|
|
14
|
+
* or the platform simply could not check it. Throwing keeps them
|
|
15
|
+
* distinguishable.
|
|
31
16
|
*/
|
|
32
17
|
|
|
33
18
|
const REASON =
|
|
@@ -35,7 +20,7 @@ const REASON =
|
|
|
35
20
|
+ 'Webhook verification is a server-side concern — the subscription secret must never reach a browser. '
|
|
36
21
|
+ 'Import them on the server from "@openwop/openwop/webhooks".';
|
|
37
22
|
|
|
38
|
-
/** @see spec/
|
|
23
|
+
/** @see spec/v2/core/webhooks.md §Verification */
|
|
39
24
|
export const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
|
|
40
25
|
|
|
41
26
|
export function verifyWebhookSignature(): never {
|
|
@@ -46,9 +31,15 @@ export function signWebhookDelivery(): never {
|
|
|
46
31
|
throw new Error(REASON);
|
|
47
32
|
}
|
|
48
33
|
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
export {
|
|
34
|
+
// The header-family readers need no Node builtin, so the browser build
|
|
35
|
+
// carries the real implementations (a browser MAY inspect which family a
|
|
36
|
+
// delivery carries; it still MUST NOT verify — no secret in a browser).
|
|
37
|
+
export {
|
|
38
|
+
WEBHOOK_HEADER_FAMILIES,
|
|
39
|
+
WEBHOOK_SIGNATURE_ALGORITHMS,
|
|
40
|
+
parseSignatureValue,
|
|
41
|
+
readWebhookHeaders,
|
|
42
|
+
} from './webhook-header-families.js';
|
|
53
43
|
|
|
54
|
-
export type { VerifyWebhookSignatureOptions, VerifyWebhookOutcome, SignedWebhookDelivery
|
|
44
|
+
export type { VerifyWebhookSignatureOptions, VerifyWebhookOutcome, SignedWebhookDelivery } from './webhook-helpers.js';
|
|
45
|
+
export type { WebhookHeaderRead, WebhookSignatureAlgorithm } from './webhook-header-families.js';
|
package/src/webhook-helpers.ts
CHANGED
|
@@ -1,42 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Webhook delivery-verification helpers per `spec/
|
|
3
|
-
* §
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Webhook delivery-verification helpers per `spec/v2/core/webhooks.md`
|
|
3
|
+
* §Verification. Receivers MUST verify both the HMAC AND the timestamp
|
|
4
|
+
* freshness before accepting a delivery — verifying HMAC alone leaves the
|
|
5
|
+
* receiver open to replay attacks.
|
|
6
6
|
*
|
|
7
|
-
* The
|
|
7
|
+
* The signing recipe (webhooks.md §Headers):
|
|
8
8
|
*
|
|
9
9
|
* hmac = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
|
|
10
|
-
* header `
|
|
11
|
-
* header `OpenWOP-
|
|
12
|
-
* header `
|
|
10
|
+
* header `OpenWOP-Signature: sha256=<hmac-hex>`
|
|
11
|
+
* header `OpenWOP-Timestamp: <unix-seconds>`
|
|
12
|
+
* header `OpenWOP-Signature-Algorithm: v1`
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `readWebhookHeaders` picks the first present header family in spec order
|
|
19
|
-
* (`OpenWOP-*`, then `X-openwop-*`, then the legacy `openwop-Webhook-*`).
|
|
14
|
+
* A host advertising both majors sends the `X-openwop-*` family beside it
|
|
15
|
+
* with identical values through the overlap; `readWebhookHeaders` accepts
|
|
16
|
+
* either family. The SDK-only `openwop-Webhook-*` names and the `v1=<hex>`
|
|
17
|
+
* value form were removed in v2 (headers.md §Removed).
|
|
20
18
|
*
|
|
21
19
|
* Verification:
|
|
22
20
|
*
|
|
23
|
-
* 1. Parse the `sha256=<hex>`
|
|
24
|
-
* 2.
|
|
25
|
-
* 3.
|
|
26
|
-
* 4.
|
|
27
|
-
*
|
|
21
|
+
* 1. Parse the `sha256=<hex>` value from the signature header.
|
|
22
|
+
* 2. Reject an unrecognized `OpenWOP-Signature-Algorithm` value (MUST).
|
|
23
|
+
* 3. Reject a timestamp more than ±window from the clock (default 5 min).
|
|
24
|
+
* 4. Recompute `HMAC-SHA256(`${timestamp}.${rawBody}`, secret)` and compare
|
|
25
|
+
* in constant time.
|
|
28
26
|
*
|
|
29
|
-
* Implementation note: this helper uses `node:crypto`'s `timingSafeEqual
|
|
30
|
-
*
|
|
31
|
-
* `
|
|
27
|
+
* Implementation note: this helper uses `node:crypto`'s `timingSafeEqual`.
|
|
28
|
+
* The browser build substitutes a stub that throws (see
|
|
29
|
+
* `webhook-helpers.browser.ts`).
|
|
32
30
|
*
|
|
33
31
|
* @module @openwop/openwop/webhook-helpers
|
|
34
32
|
*/
|
|
35
33
|
|
|
36
34
|
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
37
|
-
import { parseSignatureValue } from './webhook-header-families.js';
|
|
35
|
+
import { parseSignatureValue, WEBHOOK_SIGNATURE_ALGORITHMS } from './webhook-header-families.js';
|
|
38
36
|
|
|
39
|
-
/** Default freshness window per
|
|
37
|
+
/** Default freshness window per webhooks.md §Verification (±5 minutes). */
|
|
40
38
|
export const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
|
|
41
39
|
|
|
42
40
|
export interface VerifyWebhookSignatureOptions {
|
|
@@ -51,24 +49,38 @@ export interface VerifyWebhookSignatureOptions {
|
|
|
51
49
|
* Default `Math.floor(Date.now() / 1000)`.
|
|
52
50
|
*/
|
|
53
51
|
nowSeconds?: number;
|
|
52
|
+
/**
|
|
53
|
+
* The `OpenWOP-Signature-Algorithm` value the delivery carried, when the
|
|
54
|
+
* caller read one (`readWebhookHeaders().algorithmHeader`). An
|
|
55
|
+
* unrecognized value is rejected as `unsupported_signature_algorithm`.
|
|
56
|
+
*/
|
|
57
|
+
algorithmHeader?: string;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
export type VerifyWebhookOutcome =
|
|
57
61
|
| { valid: true }
|
|
58
|
-
| {
|
|
62
|
+
| {
|
|
63
|
+
valid: false;
|
|
64
|
+
reason:
|
|
65
|
+
| 'signature_mismatch'
|
|
66
|
+
| 'timestamp_expired'
|
|
67
|
+
| 'timestamp_too_far_in_future'
|
|
68
|
+
| 'malformed_signature_header'
|
|
69
|
+
| 'malformed_timestamp_header'
|
|
70
|
+
| 'unsupported_signature_algorithm';
|
|
71
|
+
};
|
|
59
72
|
|
|
60
73
|
/**
|
|
61
|
-
* Verify a webhook delivery
|
|
62
|
-
* recipe". Returns `{ valid: true }` on success; otherwise
|
|
74
|
+
* Verify a webhook delivery. Returns `{ valid: true }` on success; otherwise
|
|
63
75
|
* `{ valid: false, reason }` so callers can log + alert appropriately.
|
|
64
76
|
*
|
|
65
|
-
* Callers MUST pass the **raw** body bytes — JSON-parsed-then-
|
|
66
|
-
*
|
|
67
|
-
*
|
|
77
|
+
* Callers MUST pass the **raw** body bytes — JSON-parsed-then-re-serialized
|
|
78
|
+
* bodies fail verification because the host signs the exact bytes it
|
|
79
|
+
* delivered.
|
|
68
80
|
*
|
|
69
|
-
* @param secret The
|
|
70
|
-
* @param signatureHeader
|
|
71
|
-
* @param timestampHeader The
|
|
81
|
+
* @param secret The subscription secret.
|
|
82
|
+
* @param signatureHeader `OpenWOP-Signature` / `X-openwop-Signature` (`"sha256=abc123…"`); see `readWebhookHeaders`.
|
|
83
|
+
* @param timestampHeader The matching timestamp header (unix seconds as string).
|
|
72
84
|
* @param rawBody The exact request body bytes the host POSTed.
|
|
73
85
|
*/
|
|
74
86
|
export function verifyWebhookSignature(
|
|
@@ -78,33 +90,36 @@ export function verifyWebhookSignature(
|
|
|
78
90
|
rawBody: string | Buffer,
|
|
79
91
|
options: VerifyWebhookSignatureOptions = {},
|
|
80
92
|
): VerifyWebhookOutcome {
|
|
81
|
-
// 1. Parse the signature header —
|
|
82
|
-
// §"Headers") or the legacy SDK form `v1=<hex>` (RFC 0165 §C.3).
|
|
93
|
+
// 1. Parse the signature header — `sha256=<hex>` only.
|
|
83
94
|
const providedHex = parseSignatureValue(signatureHeader);
|
|
84
95
|
if (providedHex === null) {
|
|
85
96
|
return { valid: false, reason: 'malformed_signature_header' };
|
|
86
97
|
}
|
|
87
|
-
|
|
88
|
-
|
|
98
|
+
|
|
99
|
+
// 2. Reject an unrecognized scheme.
|
|
100
|
+
if (
|
|
101
|
+
options.algorithmHeader !== undefined &&
|
|
102
|
+
!(WEBHOOK_SIGNATURE_ALGORITHMS as readonly string[]).includes(options.algorithmHeader)
|
|
103
|
+
) {
|
|
104
|
+
return { valid: false, reason: 'unsupported_signature_algorithm' };
|
|
89
105
|
}
|
|
90
106
|
|
|
91
|
-
//
|
|
107
|
+
// 3. Parse the timestamp.
|
|
92
108
|
const timestamp = Number(timestampHeader);
|
|
93
109
|
if (!Number.isInteger(timestamp) || timestamp <= 0) {
|
|
94
110
|
return { valid: false, reason: 'malformed_timestamp_header' };
|
|
95
111
|
}
|
|
96
112
|
|
|
97
|
-
//
|
|
113
|
+
// 4. Freshness check.
|
|
98
114
|
const window = options.freshnessWindowSeconds ?? DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS;
|
|
99
115
|
if (window > 0) {
|
|
100
116
|
const now = options.nowSeconds ?? Math.floor(Date.now() / 1000);
|
|
101
117
|
const delta = now - timestamp;
|
|
102
118
|
if (delta > window) return { valid: false, reason: 'timestamp_expired' };
|
|
103
|
-
// Allow small future skew (within the window) but reject far-future timestamps.
|
|
104
119
|
if (delta < -window) return { valid: false, reason: 'timestamp_too_far_in_future' };
|
|
105
120
|
}
|
|
106
121
|
|
|
107
|
-
//
|
|
122
|
+
// 5. Recompute + constant-time compare.
|
|
108
123
|
const bodyStr = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8');
|
|
109
124
|
const signedBytes = `${timestamp}.${bodyStr}`;
|
|
110
125
|
const expectedHex = createHmac('sha256', secret).update(signedBytes, 'utf8').digest('hex');
|
|
@@ -122,20 +137,17 @@ export function verifyWebhookSignature(
|
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
export interface SignedWebhookDelivery {
|
|
125
|
-
/**
|
|
140
|
+
/** `sha256=<hex>` */
|
|
126
141
|
readonly signatureHeader: string;
|
|
127
|
-
/** Legacy form this SDK used to emit: `v1=<hex>` (RFC 0165 §C.3). */
|
|
128
|
-
readonly legacySignatureHeader: string;
|
|
129
142
|
readonly timestampHeader: string;
|
|
130
|
-
/**
|
|
143
|
+
/** The five `OpenWOP-*` headers a v2 host MUST send on every delivery, minus the two subscription-specific ones (`OpenWOP-Webhook-Id`, `OpenWOP-Event-Type`), plus the `X-openwop-*` twins a dual-major host emits through the overlap. */
|
|
131
144
|
readonly headers: Readonly<Record<string, string>>;
|
|
132
145
|
}
|
|
133
146
|
|
|
134
147
|
/**
|
|
135
148
|
* Compute the canonical webhook signature for a payload — useful when
|
|
136
|
-
* implementing a host (forward direction) OR when generating test
|
|
137
|
-
*
|
|
138
|
-
* inverse.
|
|
149
|
+
* implementing a host (forward direction) OR when generating test fixtures.
|
|
150
|
+
* Receivers verify via `verifyWebhookSignature`; this is the inverse.
|
|
139
151
|
*/
|
|
140
152
|
export function signWebhookDelivery(
|
|
141
153
|
secret: string,
|
|
@@ -146,7 +158,6 @@ export function signWebhookDelivery(
|
|
|
146
158
|
const hex = createHmac('sha256', secret).update(`${timestamp}.${bodyStr}`, 'utf8').digest('hex');
|
|
147
159
|
return {
|
|
148
160
|
signatureHeader: `sha256=${hex}`,
|
|
149
|
-
legacySignatureHeader: `v1=${hex}`,
|
|
150
161
|
timestampHeader: String(timestamp),
|
|
151
162
|
headers: {
|
|
152
163
|
'OpenWOP-Signature': `sha256=${hex}`,
|
|
@@ -155,11 +166,14 @@ export function signWebhookDelivery(
|
|
|
155
166
|
'X-openwop-Signature': `sha256=${hex}`,
|
|
156
167
|
'X-openwop-Timestamp': String(timestamp),
|
|
157
168
|
'X-openwop-Signature-Algorithm': 'v1',
|
|
158
|
-
'openwop-Webhook-Signature': `v1=${hex}`,
|
|
159
|
-
'openwop-Webhook-Timestamp': String(timestamp),
|
|
160
169
|
},
|
|
161
170
|
};
|
|
162
171
|
}
|
|
163
172
|
|
|
164
|
-
export {
|
|
165
|
-
|
|
173
|
+
export {
|
|
174
|
+
WEBHOOK_HEADER_FAMILIES,
|
|
175
|
+
WEBHOOK_SIGNATURE_ALGORITHMS,
|
|
176
|
+
parseSignatureValue,
|
|
177
|
+
readWebhookHeaders,
|
|
178
|
+
} from './webhook-header-families.js';
|
|
179
|
+
export type { WebhookHeaderRead, WebhookSignatureAlgorithm } from './webhook-header-families.js';
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public-registry read helpers per
|
|
3
|
-
* `spec/v1/registry-operations.md`.
|
|
4
|
-
*
|
|
5
|
-
* The OpenWOP host SDK targets the host wire surface
|
|
6
|
-
* (`/.well-known/openwop` + `/v1/runs/*` + `/v1/interrupts/*` etc).
|
|
7
|
-
* The **public node-pack registry** at `packs.openwop.dev` is a
|
|
8
|
-
* separate wire surface with its own discovery payload and pack-
|
|
9
|
-
* versioned read endpoints. This module exposes a thin typed client
|
|
10
|
-
* for that surface so adopters fetching pack manifests, indices, or
|
|
11
|
-
* signature material don't roll their own HTTP plumbing.
|
|
12
|
-
*
|
|
13
|
-
* Read-only by design — the public registry uses pull-request-driven
|
|
14
|
-
* publishing per `spec/v1/registry-operations.md` §"Submission flow"
|
|
15
|
-
* + the `registry-publish.yml` GitHub workflow. There is no write API.
|
|
16
|
-
*
|
|
17
|
-
* No auth required for public reads.
|
|
18
|
-
*
|
|
19
|
-
* @module @openwop/openwop/registry-helpers
|
|
20
|
-
*/
|
|
21
|
-
/** Public registry discovery payload per `registry-operations.md` §"Discovery". */
|
|
22
|
-
export interface RegistryDiscovery {
|
|
23
|
-
registryVersion: string;
|
|
24
|
-
protocolVersion: string;
|
|
25
|
-
name?: string;
|
|
26
|
-
operator?: string;
|
|
27
|
-
url?: string;
|
|
28
|
-
supportedNamespaces: readonly string[];
|
|
29
|
-
supportedSigningMethods: readonly string[];
|
|
30
|
-
supportedTrustModes?: readonly string[];
|
|
31
|
-
endpoints: {
|
|
32
|
-
registryIndex: string;
|
|
33
|
-
packMetadata: string;
|
|
34
|
-
versionManifest: string;
|
|
35
|
-
versionTarball: string;
|
|
36
|
-
versionSignature: string;
|
|
37
|
-
publicKey: string;
|
|
38
|
-
};
|
|
39
|
-
signingKeys?: ReadonlyArray<{
|
|
40
|
-
keyId: string;
|
|
41
|
-
algorithm: string;
|
|
42
|
-
publicKeyUrl?: string;
|
|
43
|
-
permittedNamespaces?: readonly string[];
|
|
44
|
-
operator?: string;
|
|
45
|
-
status?: string;
|
|
46
|
-
}>;
|
|
47
|
-
[key: string]: unknown;
|
|
48
|
-
}
|
|
49
|
-
/** Registry-wide index entry per `/v1/index.json` rows. */
|
|
50
|
-
export interface RegistryIndexEntry {
|
|
51
|
-
name: string;
|
|
52
|
-
latestVersion: string;
|
|
53
|
-
description?: string;
|
|
54
|
-
scope?: string;
|
|
55
|
-
[key: string]: unknown;
|
|
56
|
-
}
|
|
57
|
-
export interface RegistryIndex {
|
|
58
|
-
packs: ReadonlyArray<RegistryIndexEntry>;
|
|
59
|
-
generated?: string;
|
|
60
|
-
packCount?: number;
|
|
61
|
-
[key: string]: unknown;
|
|
62
|
-
}
|
|
63
|
-
/** Per-pack metadata document at `/v1/packs/{name}/index.json`. */
|
|
64
|
-
export interface RegistryPackMetadata {
|
|
65
|
-
name: string;
|
|
66
|
-
description?: string;
|
|
67
|
-
versions: ReadonlyArray<string>;
|
|
68
|
-
latestVersion?: string;
|
|
69
|
-
[key: string]: unknown;
|
|
70
|
-
}
|
|
71
|
-
/** Version manifest at `/v1/packs/{name}/-/{version}.json`. */
|
|
72
|
-
export interface RegistryVersionManifest {
|
|
73
|
-
name: string;
|
|
74
|
-
version: string;
|
|
75
|
-
description?: string;
|
|
76
|
-
/** SRI hash: `sha256-<43-char-b64>=`. */
|
|
77
|
-
integrity?: string;
|
|
78
|
-
signing?: {
|
|
79
|
-
method: 'ed25519' | 'manual' | string;
|
|
80
|
-
keyId: string;
|
|
81
|
-
publicKeyUrl?: string;
|
|
82
|
-
};
|
|
83
|
-
[key: string]: unknown;
|
|
84
|
-
}
|
|
85
|
-
export interface RegistryClientOptions {
|
|
86
|
-
/** Base URL for the registry. Defaults to `https://packs.openwop.dev`. */
|
|
87
|
-
baseUrl?: string;
|
|
88
|
-
/** Custom fetch implementation; defaults to `globalThis.fetch`. */
|
|
89
|
-
fetch?: typeof fetch;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Typed client for the public OpenWOP node-pack registry. Read-only;
|
|
93
|
-
* no auth required for the canonical public read surface.
|
|
94
|
-
*
|
|
95
|
-
* For host-side install-time verification (SRI + Ed25519 + lockfile),
|
|
96
|
-
* see `examples/hosts/postgres/src/pack-consumer.ts` — the registry
|
|
97
|
-
* client is the fetch surface; the consumer is the security surface.
|
|
98
|
-
*/
|
|
99
|
-
export declare class RegistryClient {
|
|
100
|
-
#private;
|
|
101
|
-
readonly baseUrl: string;
|
|
102
|
-
constructor(options?: RegistryClientOptions);
|
|
103
|
-
/** `GET /.well-known/openwop-registry` — discovery + endpoint catalog. */
|
|
104
|
-
discovery(): Promise<RegistryDiscovery>;
|
|
105
|
-
/** `GET /v1/index.json` — registry-wide pack index. */
|
|
106
|
-
index(): Promise<RegistryIndex>;
|
|
107
|
-
/** `GET /v1/packs/{name}/index.json` — per-pack metadata. */
|
|
108
|
-
pack(name: string): Promise<RegistryPackMetadata>;
|
|
109
|
-
/** `GET /v1/packs/{name}/-/{version}.json` — version manifest. */
|
|
110
|
-
version(name: string, version: string): Promise<RegistryVersionManifest>;
|
|
111
|
-
/** Fetch raw tarball bytes. Caller MUST verify SRI + signature before trust. */
|
|
112
|
-
tarball(name: string, version: string): Promise<Buffer>;
|
|
113
|
-
/** Fetch raw 64-byte Ed25519 signature bytes. */
|
|
114
|
-
signature(name: string, version: string): Promise<Buffer>;
|
|
115
|
-
/** Fetch a publisher's public key as PEM text. */
|
|
116
|
-
publicKey(keyId: string): Promise<string>;
|
|
117
|
-
}
|
|
118
|
-
//# sourceMappingURL=registry-helpers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registry-helpers.d.ts","sourceRoot":"","sources":["../src/registry-helpers.ts"],"names":[],"mappings":"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"}
|
package/dist/registry-helpers.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public-registry read helpers per
|
|
3
|
-
* `spec/v1/registry-operations.md`.
|
|
4
|
-
*
|
|
5
|
-
* The OpenWOP host SDK targets the host wire surface
|
|
6
|
-
* (`/.well-known/openwop` + `/v1/runs/*` + `/v1/interrupts/*` etc).
|
|
7
|
-
* The **public node-pack registry** at `packs.openwop.dev` is a
|
|
8
|
-
* separate wire surface with its own discovery payload and pack-
|
|
9
|
-
* versioned read endpoints. This module exposes a thin typed client
|
|
10
|
-
* for that surface so adopters fetching pack manifests, indices, or
|
|
11
|
-
* signature material don't roll their own HTTP plumbing.
|
|
12
|
-
*
|
|
13
|
-
* Read-only by design — the public registry uses pull-request-driven
|
|
14
|
-
* publishing per `spec/v1/registry-operations.md` §"Submission flow"
|
|
15
|
-
* + the `registry-publish.yml` GitHub workflow. There is no write API.
|
|
16
|
-
*
|
|
17
|
-
* No auth required for public reads.
|
|
18
|
-
*
|
|
19
|
-
* @module @openwop/openwop/registry-helpers
|
|
20
|
-
*/
|
|
21
|
-
/**
|
|
22
|
-
* Typed client for the public OpenWOP node-pack registry. Read-only;
|
|
23
|
-
* no auth required for the canonical public read surface.
|
|
24
|
-
*
|
|
25
|
-
* For host-side install-time verification (SRI + Ed25519 + lockfile),
|
|
26
|
-
* see `examples/hosts/postgres/src/pack-consumer.ts` — the registry
|
|
27
|
-
* client is the fetch surface; the consumer is the security surface.
|
|
28
|
-
*/
|
|
29
|
-
export class RegistryClient {
|
|
30
|
-
baseUrl;
|
|
31
|
-
#fetch;
|
|
32
|
-
constructor(options = {}) {
|
|
33
|
-
this.baseUrl = (options.baseUrl ?? 'https://packs.openwop.dev').replace(/\/$/, '');
|
|
34
|
-
this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
35
|
-
}
|
|
36
|
-
/** `GET /.well-known/openwop-registry` — discovery + endpoint catalog. */
|
|
37
|
-
async discovery() {
|
|
38
|
-
return this.#getJson('/.well-known/openwop-registry');
|
|
39
|
-
}
|
|
40
|
-
/** `GET /v1/index.json` — registry-wide pack index. */
|
|
41
|
-
async index() {
|
|
42
|
-
return this.#getJson('/v1/index.json');
|
|
43
|
-
}
|
|
44
|
-
/** `GET /v1/packs/{name}/index.json` — per-pack metadata. */
|
|
45
|
-
async pack(name) {
|
|
46
|
-
return this.#getJson(`/v1/packs/${encodeURIComponent(name)}/index.json`);
|
|
47
|
-
}
|
|
48
|
-
/** `GET /v1/packs/{name}/-/{version}.json` — version manifest. */
|
|
49
|
-
async version(name, version) {
|
|
50
|
-
return this.#getJson(`/v1/packs/${encodeURIComponent(name)}/-/${encodeURIComponent(version)}.json`);
|
|
51
|
-
}
|
|
52
|
-
/** Fetch raw tarball bytes. Caller MUST verify SRI + signature before trust. */
|
|
53
|
-
async tarball(name, version) {
|
|
54
|
-
return this.#getBinary(`/v1/packs/${encodeURIComponent(name)}/-/${encodeURIComponent(version)}.tgz`);
|
|
55
|
-
}
|
|
56
|
-
/** Fetch raw 64-byte Ed25519 signature bytes. */
|
|
57
|
-
async signature(name, version) {
|
|
58
|
-
return this.#getBinary(`/v1/packs/${encodeURIComponent(name)}/-/${encodeURIComponent(version)}.sig`);
|
|
59
|
-
}
|
|
60
|
-
/** Fetch a publisher's public key as PEM text. */
|
|
61
|
-
async publicKey(keyId) {
|
|
62
|
-
const res = await this.#fetch(`${this.baseUrl}/keys/${encodeURIComponent(keyId)}.pub`);
|
|
63
|
-
if (!res.ok)
|
|
64
|
-
throw new Error(`registry: GET /keys/${keyId}.pub returned ${res.status}`);
|
|
65
|
-
return res.text();
|
|
66
|
-
}
|
|
67
|
-
async #getJson(path) {
|
|
68
|
-
const res = await this.#fetch(`${this.baseUrl}${path}`, {
|
|
69
|
-
headers: { Accept: 'application/json' },
|
|
70
|
-
});
|
|
71
|
-
if (!res.ok)
|
|
72
|
-
throw new Error(`registry: GET ${path} returned ${res.status}`);
|
|
73
|
-
return (await res.json());
|
|
74
|
-
}
|
|
75
|
-
async #getBinary(path) {
|
|
76
|
-
const res = await this.#fetch(`${this.baseUrl}${path}`);
|
|
77
|
-
if (!res.ok)
|
|
78
|
-
throw new Error(`registry: GET ${path} returned ${res.status}`);
|
|
79
|
-
return Buffer.from(await res.arrayBuffer());
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
//# sourceMappingURL=registry-helpers.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registry-helpers.js","sourceRoot":"","sources":["../src/registry-helpers.ts"],"names":[],"mappings":"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"}
|