@run402/functions 2.7.0 → 2.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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/verify-webhook.d.ts +71 -0
- package/dist/verify-webhook.d.ts.map +1 -0
- package/dist/verify-webhook.js +147 -0
- package/dist/verify-webhook.js.map +1 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export type { EmailSendOptions, EmailRawOptions, EmailTemplateOptions, EmailSend
|
|
|
6
6
|
export { ai } from "./ai.js";
|
|
7
7
|
export type { GenerateImageOptions, GenerateImageResult, ImageAspect, TranslateOptions, TranslateResult, ModerateResult, } from "./ai.js";
|
|
8
8
|
export { assets } from "./assets.js";
|
|
9
|
+
export { verifyWebhook } from "./verify-webhook.js";
|
|
10
|
+
export type { VerifyWebhookOptions, VerifyWebhookResult, HeadersLike } from "./verify-webhook.js";
|
|
9
11
|
export { getRun402Context } from "./request-context.js";
|
|
10
12
|
export type { Run402RequestContext } from "./request-context.js";
|
|
11
13
|
export type { AssetPutOptions, AssetPutSource, AssetPutSourceInput, AssetRef, AssetVisibility, AssetVariant, AssetListRow, AssetsListFilter, AssetsListOptions, AssetsListResult, AssetsListSort, ImageInfo, } from "./assets.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACxD,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3G,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACxD,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3G,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIrC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAMlG,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,YAAY,EACV,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,QAAQ,EACR,eAAe,EACf,YAAY,EAEZ,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,SAAS,GACV,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC5E,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EACV,KAAK,EACL,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kCAAkC,EAClC,mCAAmC,GACpC,MAAM,YAAY,CAAC;AAKpB,OAAO,EACL,GAAG,EACH,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,gCAAgC,GACjC,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,10 @@ export { getUser, getUserId, getRole } from "./auth.js";
|
|
|
3
3
|
export { email } from "./email.js";
|
|
4
4
|
export { ai } from "./ai.js";
|
|
5
5
|
export { assets } from "./assets.js";
|
|
6
|
+
// `verifyWebhook(headers, rawBody, secret)` — verify a Run402-signed
|
|
7
|
+
// operator-notifications webhook delivery. Stripe-shape HMAC SHA256 with
|
|
8
|
+
// dual-secret rotation grace.
|
|
9
|
+
export { verifyWebhook } from "./verify-webhook.js";
|
|
6
10
|
// `getRun402Context(request)` — zero-dep helper for non-Astro Node22
|
|
7
11
|
// functions (webhooks, auth endpoints, admin tools) to read the
|
|
8
12
|
// per-request context the gateway populates as `x-run402-*` headers.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAS7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,qEAAqE;AACrE,gEAAgE;AAChE,qEAAqE;AACrE,2EAA2E;AAC3E,2DAA2D;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAiBxD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAO5E,0CAA0C;AAC1C,mEAAmE;AACnE,2DAA2D;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAOnC,OAAO,EACL,kCAAkC,EAClC,mCAAmC,GACpC,MAAM,YAAY,CAAC;AACpB,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AACxE,iDAAiD;AACjD,OAAO,EACL,GAAG,EACH,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,gCAAgC,GACjC,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAS7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,qEAAqE;AACrE,yEAAyE;AACzE,8BAA8B;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,qEAAqE;AACrE,gEAAgE;AAChE,qEAAqE;AACrE,2EAA2E;AAC3E,2DAA2D;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAiBxD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAO5E,0CAA0C;AAC1C,mEAAmE;AACnE,2DAA2D;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAOnC,OAAO,EACL,kCAAkC,EAClC,mCAAmC,GACpC,MAAM,YAAY,CAAC;AACpB,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AACxE,iDAAiD;AACjD,OAAO,EACL,GAAG,EACH,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,gCAAgC,GACjC,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify a Run402 operator-notifications webhook request.
|
|
3
|
+
*
|
|
4
|
+
* Run402 signs every webhook delivery with HMAC-SHA256 in Stripe shape:
|
|
5
|
+
* Run402-Signature: t=<unix_seconds>,v1=<hex_digest>
|
|
6
|
+
*
|
|
7
|
+
* where `<hex_digest>` is `HMAC_SHA256(secret, "${t}.${rawBody}")`.
|
|
8
|
+
*
|
|
9
|
+
* Receivers should call `verifyWebhook(headers, rawBody, secret)` BEFORE
|
|
10
|
+
* parsing or trusting the body. Pass the EXACT raw request body — re-
|
|
11
|
+
* serializing JSON will change byte order and break the HMAC.
|
|
12
|
+
*
|
|
13
|
+
* Dual-secret rotation: pass `previousSecret` (returned from
|
|
14
|
+
* `POST /agent/v1/webhook-secret/rotate` before rotation) to accept
|
|
15
|
+
* signatures created with either secret during the 24h grace window.
|
|
16
|
+
*
|
|
17
|
+
* Replay protection: the function rejects signatures whose `t` is more
|
|
18
|
+
* than `toleranceSeconds` (default 300 = 5 minutes) away from the
|
|
19
|
+
* current time.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { verifyWebhook } from "@run402/functions";
|
|
24
|
+
*
|
|
25
|
+
* export default async (req: Request) => {
|
|
26
|
+
* const rawBody = await req.text();
|
|
27
|
+
* const result = verifyWebhook(req.headers, rawBody, process.env.RUN402_WEBHOOK_SECRET!);
|
|
28
|
+
* if (!result.valid) {
|
|
29
|
+
* return new Response(`bad signature: ${result.reason}`, { status: 401 });
|
|
30
|
+
* }
|
|
31
|
+
* const event = JSON.parse(rawBody);
|
|
32
|
+
* // …handle event…
|
|
33
|
+
* return new Response("ok");
|
|
34
|
+
* };
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export interface VerifyWebhookOptions {
|
|
38
|
+
/** Maximum age of the signed timestamp, in seconds. Default 300 (5 min). */
|
|
39
|
+
toleranceSeconds?: number;
|
|
40
|
+
/** Optional previous secret accepted during a 24h rotation grace window. */
|
|
41
|
+
previousSecret?: string | null;
|
|
42
|
+
/** Override the current Unix timestamp (for tests). */
|
|
43
|
+
nowSeconds?: () => number;
|
|
44
|
+
}
|
|
45
|
+
export type VerifyWebhookResult = {
|
|
46
|
+
valid: true;
|
|
47
|
+
timestamp: number;
|
|
48
|
+
secret_used: "current" | "previous";
|
|
49
|
+
} | {
|
|
50
|
+
valid: false;
|
|
51
|
+
reason: "missing_signature_header" | "malformed_signature_header" | "missing_timestamp" | "missing_v1" | "timestamp_out_of_tolerance" | "signature_mismatch";
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Verify a Run402-signed webhook.
|
|
55
|
+
*
|
|
56
|
+
* @param headers Either an instance of `Headers` (Web API), a Node
|
|
57
|
+
* `IncomingHttpHeaders`-shaped record, or any object with
|
|
58
|
+
* `get(name)` and case-insensitive header access.
|
|
59
|
+
* @param rawBody The exact raw request body bytes received. Must NOT be
|
|
60
|
+
* re-serialized from a parsed JSON object — byte-level
|
|
61
|
+
* equality with the signed payload is required.
|
|
62
|
+
* @param secret The current webhook signing secret returned by
|
|
63
|
+
* `POST /agent/v1/webhook-secret/rotate`.
|
|
64
|
+
* @param options Optional behavior tweaks.
|
|
65
|
+
*/
|
|
66
|
+
export declare function verifyWebhook(headers: HeadersLike, rawBody: string, secret: string, options?: VerifyWebhookOptions): VerifyWebhookResult;
|
|
67
|
+
/** Anything we can read a header from. */
|
|
68
|
+
export type HeadersLike = Headers | Record<string, string | string[] | undefined> | {
|
|
69
|
+
get(name: string): string | null;
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=verify-webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-webhook.d.ts","sourceRoot":"","sources":["../src/verify-webhook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH,MAAM,WAAW,oBAAoB;IACnC,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,SAAS,GAAG,UAAU,CAAA;CAAE,GACvE;IACE,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EACF,0BAA0B,GAC1B,4BAA4B,GAC5B,mBAAmB,GACnB,YAAY,GACZ,4BAA4B,GAC5B,oBAAoB,CAAC;CAC1B,CAAC;AAEN;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,oBAAyB,GACjC,mBAAmB,CAoCrB;AAOD,0CAA0C;AAC1C,MAAM,MAAM,WAAW,GACnB,OAAO,GACP,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAC7C;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify a Run402 operator-notifications webhook request.
|
|
3
|
+
*
|
|
4
|
+
* Run402 signs every webhook delivery with HMAC-SHA256 in Stripe shape:
|
|
5
|
+
* Run402-Signature: t=<unix_seconds>,v1=<hex_digest>
|
|
6
|
+
*
|
|
7
|
+
* where `<hex_digest>` is `HMAC_SHA256(secret, "${t}.${rawBody}")`.
|
|
8
|
+
*
|
|
9
|
+
* Receivers should call `verifyWebhook(headers, rawBody, secret)` BEFORE
|
|
10
|
+
* parsing or trusting the body. Pass the EXACT raw request body — re-
|
|
11
|
+
* serializing JSON will change byte order and break the HMAC.
|
|
12
|
+
*
|
|
13
|
+
* Dual-secret rotation: pass `previousSecret` (returned from
|
|
14
|
+
* `POST /agent/v1/webhook-secret/rotate` before rotation) to accept
|
|
15
|
+
* signatures created with either secret during the 24h grace window.
|
|
16
|
+
*
|
|
17
|
+
* Replay protection: the function rejects signatures whose `t` is more
|
|
18
|
+
* than `toleranceSeconds` (default 300 = 5 minutes) away from the
|
|
19
|
+
* current time.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { verifyWebhook } from "@run402/functions";
|
|
24
|
+
*
|
|
25
|
+
* export default async (req: Request) => {
|
|
26
|
+
* const rawBody = await req.text();
|
|
27
|
+
* const result = verifyWebhook(req.headers, rawBody, process.env.RUN402_WEBHOOK_SECRET!);
|
|
28
|
+
* if (!result.valid) {
|
|
29
|
+
* return new Response(`bad signature: ${result.reason}`, { status: 401 });
|
|
30
|
+
* }
|
|
31
|
+
* const event = JSON.parse(rawBody);
|
|
32
|
+
* // …handle event…
|
|
33
|
+
* return new Response("ok");
|
|
34
|
+
* };
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
38
|
+
/**
|
|
39
|
+
* Verify a Run402-signed webhook.
|
|
40
|
+
*
|
|
41
|
+
* @param headers Either an instance of `Headers` (Web API), a Node
|
|
42
|
+
* `IncomingHttpHeaders`-shaped record, or any object with
|
|
43
|
+
* `get(name)` and case-insensitive header access.
|
|
44
|
+
* @param rawBody The exact raw request body bytes received. Must NOT be
|
|
45
|
+
* re-serialized from a parsed JSON object — byte-level
|
|
46
|
+
* equality with the signed payload is required.
|
|
47
|
+
* @param secret The current webhook signing secret returned by
|
|
48
|
+
* `POST /agent/v1/webhook-secret/rotate`.
|
|
49
|
+
* @param options Optional behavior tweaks.
|
|
50
|
+
*/
|
|
51
|
+
export function verifyWebhook(headers, rawBody, secret, options = {}) {
|
|
52
|
+
const tolerance = options.toleranceSeconds ?? 300;
|
|
53
|
+
const nowSeconds = options.nowSeconds ?? (() => Math.floor(Date.now() / 1000));
|
|
54
|
+
const headerValue = getHeader(headers, "run402-signature");
|
|
55
|
+
if (headerValue === null) {
|
|
56
|
+
// Header not present at all.
|
|
57
|
+
return { valid: false, reason: "missing_signature_header" };
|
|
58
|
+
}
|
|
59
|
+
const parsed = parseSignatureHeader(headerValue);
|
|
60
|
+
if (parsed === null) {
|
|
61
|
+
return { valid: false, reason: "malformed_signature_header" };
|
|
62
|
+
}
|
|
63
|
+
if (parsed.t === null) {
|
|
64
|
+
return { valid: false, reason: "missing_timestamp" };
|
|
65
|
+
}
|
|
66
|
+
if (parsed.v1Hex === null) {
|
|
67
|
+
return { valid: false, reason: "missing_v1" };
|
|
68
|
+
}
|
|
69
|
+
const now = nowSeconds();
|
|
70
|
+
if (Math.abs(now - parsed.t) > tolerance) {
|
|
71
|
+
return { valid: false, reason: "timestamp_out_of_tolerance" };
|
|
72
|
+
}
|
|
73
|
+
const signedPayload = `${parsed.t}.${rawBody}`;
|
|
74
|
+
if (constantTimeEqualHex(parsed.v1Hex, computeHmacHex(secret, signedPayload))) {
|
|
75
|
+
return { valid: true, timestamp: parsed.t, secret_used: "current" };
|
|
76
|
+
}
|
|
77
|
+
if (options.previousSecret) {
|
|
78
|
+
if (constantTimeEqualHex(parsed.v1Hex, computeHmacHex(options.previousSecret, signedPayload))) {
|
|
79
|
+
return { valid: true, timestamp: parsed.t, secret_used: "previous" };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { valid: false, reason: "signature_mismatch" };
|
|
83
|
+
}
|
|
84
|
+
function getHeader(headers, name) {
|
|
85
|
+
// Web Headers: has .get and case-insensitive.
|
|
86
|
+
if (typeof headers.get === "function" && headers.get.length === 1) {
|
|
87
|
+
return headers.get(name);
|
|
88
|
+
}
|
|
89
|
+
// Plain object: case-insensitive lookup.
|
|
90
|
+
const lower = name.toLowerCase();
|
|
91
|
+
const rec = headers;
|
|
92
|
+
for (const key of Object.keys(rec)) {
|
|
93
|
+
if (key.toLowerCase() === lower) {
|
|
94
|
+
const v = rec[key];
|
|
95
|
+
if (Array.isArray(v))
|
|
96
|
+
return v[0] ?? null;
|
|
97
|
+
return v ?? null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
function parseSignatureHeader(value) {
|
|
103
|
+
// Format: t=<unix>,v1=<hex>[,v2=...]
|
|
104
|
+
// Use comma + equals splitting; reject anything else.
|
|
105
|
+
const trimmed = value.trim();
|
|
106
|
+
if (trimmed === "")
|
|
107
|
+
return null;
|
|
108
|
+
const parts = trimmed.split(",");
|
|
109
|
+
let t = null;
|
|
110
|
+
let v1Hex = null;
|
|
111
|
+
for (const raw of parts) {
|
|
112
|
+
const segment = raw.trim();
|
|
113
|
+
const eq = segment.indexOf("=");
|
|
114
|
+
if (eq <= 0)
|
|
115
|
+
return null;
|
|
116
|
+
const key = segment.slice(0, eq).trim();
|
|
117
|
+
const val = segment.slice(eq + 1).trim();
|
|
118
|
+
if (!key || !val)
|
|
119
|
+
return null;
|
|
120
|
+
if (key === "t") {
|
|
121
|
+
const n = Number(val);
|
|
122
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0)
|
|
123
|
+
return null;
|
|
124
|
+
t = n;
|
|
125
|
+
}
|
|
126
|
+
else if (key === "v1") {
|
|
127
|
+
if (!/^[0-9a-f]{64}$/i.test(val))
|
|
128
|
+
return null;
|
|
129
|
+
v1Hex = val.toLowerCase();
|
|
130
|
+
}
|
|
131
|
+
// Unknown keys ignored — forward-compat for future v2 schemes.
|
|
132
|
+
}
|
|
133
|
+
return { t, v1Hex };
|
|
134
|
+
}
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Crypto.
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
function computeHmacHex(secret, payload) {
|
|
139
|
+
return createHmac("sha256", secret).update(payload).digest("hex");
|
|
140
|
+
}
|
|
141
|
+
function constantTimeEqualHex(a, b) {
|
|
142
|
+
if (a.length !== b.length)
|
|
143
|
+
return false;
|
|
144
|
+
// Convert both to Buffer of equal length, then timingSafeEqual.
|
|
145
|
+
return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=verify-webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-webhook.js","sourceRoot":"","sources":["../src/verify-webhook.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@run402/functions",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "In-function helper library for Run402 serverless functions — db, adminDb, getUser, email, ai, assets. Auto-bundled into deployed functions; also installable for local TypeScript autocomplete.",
|
|
3
|
+
"version": "2.9.0",
|
|
4
|
+
"description": "In-function helper library for Run402 serverless functions — db, adminDb, getUser, email, ai, assets, verifyWebhook. Auto-bundled into deployed functions; also installable for local TypeScript autocomplete.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|