@usequeek/app-sdk 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/delivery.d.ts +20 -2
- package/dist/delivery.js +31 -3
- package/dist/hono.d.ts +5 -2
- package/dist/hono.js +10 -2
- package/dist/index.d.ts +1 -1
- package/dist/install-handlers.d.ts +5 -0
- package/dist/install-handlers.js +26 -4
- package/dist/merchant-schema.d.ts +11477 -4881
- package/dist/webhooks.d.ts +9 -3
- package/dist/webhooks.js +62 -23
- package/package.json +1 -1
package/dist/webhooks.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type DeliveryErrorHandler, type DeliveryResult, type WebhookDelivery } from "./delivery.js";
|
|
2
2
|
import type { InstallationRecord, InstallationStore } from "./store.js";
|
|
3
3
|
/**
|
|
4
4
|
* Framework-agnostic receiver for Queek topic deliveries (one signed POST
|
|
@@ -79,6 +79,11 @@ export interface WebhookHandlerOptions {
|
|
|
79
79
|
* 200 for a handled, repeated or unhandled-topic delivery; 500 when your
|
|
80
80
|
* handler throws (the claim is released so Queek's retry can land).
|
|
81
81
|
*
|
|
82
|
+
* `method` `GET` or `HEAD` is the review probe: 200 `{ ok: true }` with
|
|
83
|
+
* `Cache-Control: no-store` and no handler; with a signature (over the
|
|
84
|
+
* empty body) 200 when it verifies against an installation secret and 401
|
|
85
|
+
* when not.
|
|
86
|
+
*
|
|
82
87
|
* @example
|
|
83
88
|
* const result = await handleWebhookDelivery(
|
|
84
89
|
* { rawBody, headers },
|
|
@@ -93,10 +98,11 @@ export interface WebhookHandlerOptions {
|
|
|
93
98
|
* );
|
|
94
99
|
* // reply with result.status and result.body
|
|
95
100
|
*/
|
|
96
|
-
export declare function handleWebhookDelivery(input:
|
|
101
|
+
export declare function handleWebhookDelivery(input: WebhookDelivery, options: WebhookHandlerOptions): Promise<DeliveryResult>;
|
|
97
102
|
/**
|
|
98
103
|
* Layer 2: the Web-standard wrapper, built ONLY on layer 1 — reads
|
|
99
104
|
* `await request.arrayBuffer()` + headers, calls the core, builds the
|
|
100
|
-
* `Response`.
|
|
105
|
+
* `Response`. `POST` is a delivery; `GET` and `HEAD` are the review probe
|
|
106
|
+
* (see `handleWebhookDelivery`); anything else answers 405.
|
|
101
107
|
*/
|
|
102
108
|
export declare function handleWebhookRequest(request: Request, options: WebhookHandlerOptions): Promise<Response>;
|
package/dist/webhooks.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeBody, readHeader, reportDeliveryFailure, toResponse, } from "./delivery.js";
|
|
1
|
+
import { decodeBody, isProbeMethod, isUnsignedProbe, probeResult, readHeader, reportDeliveryFailure, toResponse, } from "./delivery.js";
|
|
2
2
|
import { isFreshTimestamp, MAX_TIMESTAMP_SKEW_SECONDS, verifyQueekSignature, WEBHOOK_ID_HEADER, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_TIMESTAMP_HEADER, } from "./signatures.js";
|
|
3
3
|
/**
|
|
4
4
|
* Framework-agnostic receiver for Queek topic deliveries (one signed POST
|
|
@@ -57,6 +57,50 @@ async function defaultResolveSecret(store, rawBody, headers, envelope) {
|
|
|
57
57
|
}
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the endpoint secret that signed a delivery and verify freshness
|
|
62
|
+
* and the MAC against it. The single verification path of a delivery AND of
|
|
63
|
+
* a signed review probe (empty body, no envelope).
|
|
64
|
+
*/
|
|
65
|
+
async function verifyAgainstResolvedSecret(options, rawBody, headers, envelope) {
|
|
66
|
+
const resolution = options.resolveSecret
|
|
67
|
+
? await options.resolveSecret(envelope, rawBody, headers)
|
|
68
|
+
: await defaultResolveSecret(options.store, rawBody, headers, envelope);
|
|
69
|
+
if (!resolution) {
|
|
70
|
+
return { ok: false, error: "unknown_installation" };
|
|
71
|
+
}
|
|
72
|
+
// Freshness is enforced HERE, once, against the resolved secret — the
|
|
73
|
+
// lookup above deliberately skips it so a stale delivery cannot be
|
|
74
|
+
// misattributed before it is rejected.
|
|
75
|
+
const now = options.nowSeconds ?? Math.floor(Date.now() / 1000);
|
|
76
|
+
if (!isFreshTimestamp(headers.timestamp, now, options.maxSkewSeconds ?? MAX_TIMESTAMP_SKEW_SECONDS)) {
|
|
77
|
+
return { ok: false, error: "stale timestamp" };
|
|
78
|
+
}
|
|
79
|
+
// Freshness was enforced above; this re-checks the MAC only, so a
|
|
80
|
+
// custom resolver cannot claim an installation without its secret.
|
|
81
|
+
if (!verifyQueekSignature({ ...headers, body: rawBody, secret: resolution.secret }, { skipFreshnessCheck: true })) {
|
|
82
|
+
return { ok: false, error: "signature mismatch" };
|
|
83
|
+
}
|
|
84
|
+
return { ok: true, resolution };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The review probe (`GET` / `HEAD`): 200 without running a handler or
|
|
88
|
+
* touching the store. A probe that carries a signature is verified like a
|
|
89
|
+
* delivery, over the empty body and against the stored endpoint secrets:
|
|
90
|
+
* 200 when it verifies, 401 when not. A valid probe claims nothing.
|
|
91
|
+
*/
|
|
92
|
+
async function probeDelivery(input, options) {
|
|
93
|
+
if (isUnsignedProbe(input.headers))
|
|
94
|
+
return probeResult(200);
|
|
95
|
+
const id = readHeader(input.headers, WEBHOOK_ID_HEADER);
|
|
96
|
+
const timestamp = readHeader(input.headers, WEBHOOK_TIMESTAMP_HEADER);
|
|
97
|
+
const signatureHeader = readHeader(input.headers, WEBHOOK_SIGNATURE_HEADER);
|
|
98
|
+
if (!id || !timestamp || !signatureHeader) {
|
|
99
|
+
return probeResult(401, "missing signature headers");
|
|
100
|
+
}
|
|
101
|
+
const verified = await verifyAgainstResolvedSecret(options, "", { id, timestamp, signatureHeader }, null);
|
|
102
|
+
return verified.ok ? probeResult(200) : probeResult(401, verified.error);
|
|
103
|
+
}
|
|
60
104
|
/**
|
|
61
105
|
* Layer 1: handle one signed Queek topic delivery from plain data — no
|
|
62
106
|
* `Request`, no framework. Same options, same behaviour, same
|
|
@@ -67,6 +111,11 @@ async function defaultResolveSecret(store, rawBody, headers, envelope) {
|
|
|
67
111
|
* 200 for a handled, repeated or unhandled-topic delivery; 500 when your
|
|
68
112
|
* handler throws (the claim is released so Queek's retry can land).
|
|
69
113
|
*
|
|
114
|
+
* `method` `GET` or `HEAD` is the review probe: 200 `{ ok: true }` with
|
|
115
|
+
* `Cache-Control: no-store` and no handler; with a signature (over the
|
|
116
|
+
* empty body) 200 when it verifies against an installation secret and 401
|
|
117
|
+
* when not.
|
|
118
|
+
*
|
|
70
119
|
* @example
|
|
71
120
|
* const result = await handleWebhookDelivery(
|
|
72
121
|
* { rawBody, headers },
|
|
@@ -82,7 +131,8 @@ async function defaultResolveSecret(store, rawBody, headers, envelope) {
|
|
|
82
131
|
* // reply with result.status and result.body
|
|
83
132
|
*/
|
|
84
133
|
export async function handleWebhookDelivery(input, options) {
|
|
85
|
-
|
|
134
|
+
if (isProbeMethod(input.method))
|
|
135
|
+
return probeDelivery(input, options);
|
|
86
136
|
const rawBody = decodeBody(input.rawBody);
|
|
87
137
|
const id = readHeader(input.headers, WEBHOOK_ID_HEADER);
|
|
88
138
|
const timestamp = readHeader(input.headers, WEBHOOK_TIMESTAMP_HEADER);
|
|
@@ -103,24 +153,10 @@ export async function handleWebhookDelivery(input, options) {
|
|
|
103
153
|
return { status: 400, body: { ok: false, error: "invalid_envelope" } };
|
|
104
154
|
}
|
|
105
155
|
const headers = { id, timestamp, signatureHeader };
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return { status: 401, body: { ok: false, error: "unknown_installation" } };
|
|
111
|
-
}
|
|
112
|
-
// Freshness is enforced HERE, once, against the resolved secret — the
|
|
113
|
-
// lookup above deliberately skips it so a stale delivery cannot be
|
|
114
|
-
// misattributed before it is rejected.
|
|
115
|
-
const now = options.nowSeconds ?? Math.floor(Date.now() / 1000);
|
|
116
|
-
if (!isFreshTimestamp(timestamp, now, maxSkew)) {
|
|
117
|
-
return { status: 401, body: { ok: false, error: "stale timestamp" } };
|
|
118
|
-
}
|
|
119
|
-
// Freshness was enforced above; this re-checks the MAC only, so a
|
|
120
|
-
// custom resolver cannot claim an installation without its secret.
|
|
121
|
-
if (!verifyQueekSignature({ ...headers, body: rawBody, secret: resolution.secret }, { skipFreshnessCheck: true })) {
|
|
122
|
-
return { status: 401, body: { ok: false, error: "signature mismatch" } };
|
|
123
|
-
}
|
|
156
|
+
const verified = await verifyAgainstResolvedSecret(options, rawBody, headers, envelope);
|
|
157
|
+
if (!verified.ok)
|
|
158
|
+
return { status: 401, body: { ok: false, error: verified.error } };
|
|
159
|
+
const resolution = verified.resolution;
|
|
124
160
|
const installation = await options.store.getInstallation(resolution.installationId);
|
|
125
161
|
if (!installation) {
|
|
126
162
|
return { status: 401, body: { ok: false, error: "unknown_installation" } };
|
|
@@ -151,14 +187,17 @@ export async function handleWebhookDelivery(input, options) {
|
|
|
151
187
|
/**
|
|
152
188
|
* Layer 2: the Web-standard wrapper, built ONLY on layer 1 — reads
|
|
153
189
|
* `await request.arrayBuffer()` + headers, calls the core, builds the
|
|
154
|
-
* `Response`.
|
|
190
|
+
* `Response`. `POST` is a delivery; `GET` and `HEAD` are the review probe
|
|
191
|
+
* (see `handleWebhookDelivery`); anything else answers 405.
|
|
155
192
|
*/
|
|
156
193
|
export async function handleWebhookRequest(request, options) {
|
|
157
|
-
|
|
194
|
+
const method = request.method.toUpperCase();
|
|
195
|
+
if (method !== "POST" && !isProbeMethod(method)) {
|
|
158
196
|
return toResponse({ status: 405, body: { ok: false, error: "method_not_allowed" } });
|
|
159
197
|
}
|
|
160
198
|
return toResponse(await handleWebhookDelivery({
|
|
161
199
|
rawBody: new Uint8Array(await request.arrayBuffer()),
|
|
162
200
|
headers: request.headers,
|
|
163
|
-
|
|
201
|
+
method,
|
|
202
|
+
}, options), method);
|
|
164
203
|
}
|