@phala/pay 0.8.1 → 0.8.2
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/server/webhook.d.ts
CHANGED
|
@@ -43,9 +43,9 @@ export interface ConstructEventOptions {
|
|
|
43
43
|
expectedAccount: string;
|
|
44
44
|
/** The mode of the endpoint receiving it: an event of the other mode fails closed. */
|
|
45
45
|
expectedLivemode: boolean;
|
|
46
|
-
/**
|
|
46
|
+
/** Finite, non-negative seconds a delivery's timestamp may differ from now; default 300. */
|
|
47
47
|
tolerance?: number;
|
|
48
|
-
/**
|
|
48
|
+
/** Finite current time in Unix seconds; for tests. */
|
|
49
49
|
now?: number;
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/server/webhook.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,mEAAmE;AACnE,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAkB,IAAI,2BAA2B;CAClD;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/D;;;OAGG;IACH,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CAC1F;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,eAAe,EAAE,MAAM,CAAC;IACxB,sFAAsF;IACtF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/server/webhook.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,mEAAmE;AACnE,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAkB,IAAI,2BAA2B;CAClD;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/D;;;OAGG;IACH,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CAC1F;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,eAAe,EAAE,MAAM,CAAC;IACxB,sFAAsF;IACtF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,GAAG,UAAU,EAC5B,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EAChE,UAAU,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACtC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAgGvB"}
|
package/dist/server/webhook.js
CHANGED
|
@@ -21,6 +21,11 @@ export async function constructEvent(payload, headers, publicKeys, options) {
|
|
|
21
21
|
if (options.expectedAccount === "") {
|
|
22
22
|
throw new TypeError("expectedAccount is required");
|
|
23
23
|
}
|
|
24
|
+
const now = options.now ?? Math.floor(Date.now() / 1000);
|
|
25
|
+
const tolerance = options.tolerance ?? 300;
|
|
26
|
+
if (!Number.isFinite(now) || !Number.isFinite(tolerance) || tolerance < 0) {
|
|
27
|
+
throw new TypeError("webhook now must be finite and tolerance finite and non-negative");
|
|
28
|
+
}
|
|
24
29
|
const body = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
|
|
25
30
|
const id = header(headers, "webhook-id");
|
|
26
31
|
const timestamp = header(headers, "webhook-timestamp");
|
|
@@ -28,11 +33,10 @@ export async function constructEvent(payload, headers, publicKeys, options) {
|
|
|
28
33
|
if (id === undefined || timestamp === undefined || signatures === undefined) {
|
|
29
34
|
throw new WebhookSignatureError("webhook headers missing");
|
|
30
35
|
}
|
|
31
|
-
if (!/^\d+$/.test(timestamp)) {
|
|
36
|
+
if (!/^\d+$/.test(timestamp) || !Number.isSafeInteger(Number(timestamp))) {
|
|
32
37
|
throw new WebhookSignatureError("webhook timestamp malformed");
|
|
33
38
|
}
|
|
34
|
-
|
|
35
|
-
if (Math.abs(now - Number(timestamp)) > (options.tolerance ?? 300)) {
|
|
39
|
+
if (Math.abs(now - Number(timestamp)) > tolerance) {
|
|
36
40
|
throw new WebhookSignatureError("webhook timestamp outside tolerance");
|
|
37
41
|
}
|
|
38
42
|
const signed = concat(new TextEncoder().encode(`${id}.${timestamp}.`), body);
|
|
@@ -63,12 +67,12 @@ export async function constructEvent(payload, headers, publicKeys, options) {
|
|
|
63
67
|
catch {
|
|
64
68
|
throw new TypeError("webhook body is not an event");
|
|
65
69
|
}
|
|
66
|
-
if (
|
|
70
|
+
if (!isRecord(event)) {
|
|
67
71
|
throw new TypeError("webhook body is not an event");
|
|
68
72
|
}
|
|
69
73
|
const e = event;
|
|
70
74
|
const data = e["data"];
|
|
71
|
-
const object =
|
|
75
|
+
const object = isRecord(data) ? data["object"] : null;
|
|
72
76
|
const { id: eventId, type, account, livemode, created, actor, request } = e;
|
|
73
77
|
if (e["object"] !== "event" ||
|
|
74
78
|
typeof eventId !== "string" ||
|
|
@@ -77,8 +81,7 @@ export async function constructEvent(payload, headers, publicKeys, options) {
|
|
|
77
81
|
typeof livemode !== "boolean" ||
|
|
78
82
|
typeof created !== "number" ||
|
|
79
83
|
!Number.isSafeInteger(created) ||
|
|
80
|
-
|
|
81
|
-
object === null ||
|
|
84
|
+
!isRecord(object) ||
|
|
82
85
|
typeof actor !== "string" ||
|
|
83
86
|
!(request === null || isRequest(request))) {
|
|
84
87
|
throw new TypeError("webhook body is not an event");
|
|
@@ -92,7 +95,7 @@ export async function constructEvent(payload, headers, publicKeys, options) {
|
|
|
92
95
|
if (livemode !== options.expectedLivemode) {
|
|
93
96
|
throw new WebhookSignatureError("webhook event is for the other mode");
|
|
94
97
|
}
|
|
95
|
-
const previous = data["previous_attributes"];
|
|
98
|
+
const previous = isRecord(data) ? data["previous_attributes"] : undefined;
|
|
96
99
|
return {
|
|
97
100
|
id: eventId,
|
|
98
101
|
object: "event",
|
|
@@ -103,10 +106,8 @@ export async function constructEvent(payload, headers, publicKeys, options) {
|
|
|
103
106
|
actor,
|
|
104
107
|
request,
|
|
105
108
|
data: {
|
|
106
|
-
object
|
|
107
|
-
...(
|
|
108
|
-
? { previous_attributes: previous }
|
|
109
|
-
: {}),
|
|
109
|
+
object,
|
|
110
|
+
...(isRecord(previous) ? { previous_attributes: previous } : {}),
|
|
110
111
|
},
|
|
111
112
|
};
|
|
112
113
|
}
|
|
@@ -121,8 +122,11 @@ function header(headers, name) {
|
|
|
121
122
|
}
|
|
122
123
|
return undefined;
|
|
123
124
|
}
|
|
125
|
+
function isRecord(value) {
|
|
126
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
127
|
+
}
|
|
124
128
|
function isRequest(value) {
|
|
125
|
-
if (
|
|
129
|
+
if (!isRecord(value)) {
|
|
126
130
|
return false;
|
|
127
131
|
}
|
|
128
132
|
const { id, idempotency_key: key } = value;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/server/webhook.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,mEAAmE;AACnE,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC5B,IAAI,GAAG,uBAAuB,CAAC;CAClD;AAsCD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAA4B,EAC5B,OAAgE,EAChE,UAAsC,EACtC,OAA8B;IAE9B,IAAI,OAAO,CAAC,eAAe,KAAK,EAAE,EAAE,CAAC;QACnC,MAAM,IAAI,SAAS,CAAC,6BAA6B,CAAC,CAAC;IACrD,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACvF,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IACxD,IAAI,EAAE,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC5E,MAAM,IAAI,qBAAqB,CAAC,yBAAyB,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/server/webhook.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/server/webhook.ts
CHANGED
|
@@ -40,9 +40,9 @@ export interface ConstructEventOptions {
|
|
|
40
40
|
expectedAccount: string;
|
|
41
41
|
/** The mode of the endpoint receiving it: an event of the other mode fails closed. */
|
|
42
42
|
expectedLivemode: boolean;
|
|
43
|
-
/**
|
|
43
|
+
/** Finite, non-negative seconds a delivery's timestamp may differ from now; default 300. */
|
|
44
44
|
tolerance?: number;
|
|
45
|
-
/**
|
|
45
|
+
/** Finite current time in Unix seconds; for tests. */
|
|
46
46
|
now?: number;
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -64,6 +64,11 @@ export async function constructEvent(
|
|
|
64
64
|
if (options.expectedAccount === "") {
|
|
65
65
|
throw new TypeError("expectedAccount is required");
|
|
66
66
|
}
|
|
67
|
+
const now = options.now ?? Math.floor(Date.now() / 1000);
|
|
68
|
+
const tolerance = options.tolerance ?? 300;
|
|
69
|
+
if (!Number.isFinite(now) || !Number.isFinite(tolerance) || tolerance < 0) {
|
|
70
|
+
throw new TypeError("webhook now must be finite and tolerance finite and non-negative");
|
|
71
|
+
}
|
|
67
72
|
const body = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
|
|
68
73
|
const id = header(headers, "webhook-id");
|
|
69
74
|
const timestamp = header(headers, "webhook-timestamp");
|
|
@@ -71,11 +76,10 @@ export async function constructEvent(
|
|
|
71
76
|
if (id === undefined || timestamp === undefined || signatures === undefined) {
|
|
72
77
|
throw new WebhookSignatureError("webhook headers missing");
|
|
73
78
|
}
|
|
74
|
-
if (!/^\d+$/.test(timestamp)) {
|
|
79
|
+
if (!/^\d+$/.test(timestamp) || !Number.isSafeInteger(Number(timestamp))) {
|
|
75
80
|
throw new WebhookSignatureError("webhook timestamp malformed");
|
|
76
81
|
}
|
|
77
|
-
|
|
78
|
-
if (Math.abs(now - Number(timestamp)) > (options.tolerance ?? 300)) {
|
|
82
|
+
if (Math.abs(now - Number(timestamp)) > tolerance) {
|
|
79
83
|
throw new WebhookSignatureError("webhook timestamp outside tolerance");
|
|
80
84
|
}
|
|
81
85
|
const signed = concat(new TextEncoder().encode(`${id}.${timestamp}.`), body);
|
|
@@ -107,13 +111,12 @@ export async function constructEvent(
|
|
|
107
111
|
} catch {
|
|
108
112
|
throw new TypeError("webhook body is not an event");
|
|
109
113
|
}
|
|
110
|
-
if (
|
|
114
|
+
if (!isRecord(event)) {
|
|
111
115
|
throw new TypeError("webhook body is not an event");
|
|
112
116
|
}
|
|
113
|
-
const e = event
|
|
117
|
+
const e = event;
|
|
114
118
|
const data = e["data"];
|
|
115
|
-
const object: unknown =
|
|
116
|
-
typeof data === "object" && data !== null ? (data as Record<string, unknown>)["object"] : null;
|
|
119
|
+
const object: unknown = isRecord(data) ? data["object"] : null;
|
|
117
120
|
const { id: eventId, type, account, livemode, created, actor, request } = e;
|
|
118
121
|
if (
|
|
119
122
|
e["object"] !== "event" ||
|
|
@@ -123,8 +126,7 @@ export async function constructEvent(
|
|
|
123
126
|
typeof livemode !== "boolean" ||
|
|
124
127
|
typeof created !== "number" ||
|
|
125
128
|
!Number.isSafeInteger(created) ||
|
|
126
|
-
|
|
127
|
-
object === null ||
|
|
129
|
+
!isRecord(object) ||
|
|
128
130
|
typeof actor !== "string" ||
|
|
129
131
|
!(request === null || isRequest(request))
|
|
130
132
|
) {
|
|
@@ -139,7 +141,7 @@ export async function constructEvent(
|
|
|
139
141
|
if (livemode !== options.expectedLivemode) {
|
|
140
142
|
throw new WebhookSignatureError("webhook event is for the other mode");
|
|
141
143
|
}
|
|
142
|
-
const previous = (data
|
|
144
|
+
const previous = isRecord(data) ? data["previous_attributes"] : undefined;
|
|
143
145
|
return {
|
|
144
146
|
id: eventId,
|
|
145
147
|
object: "event",
|
|
@@ -150,10 +152,8 @@ export async function constructEvent(
|
|
|
150
152
|
actor,
|
|
151
153
|
request,
|
|
152
154
|
data: {
|
|
153
|
-
object
|
|
154
|
-
...(
|
|
155
|
-
? { previous_attributes: previous as Record<string, unknown> }
|
|
156
|
-
: {}),
|
|
155
|
+
object,
|
|
156
|
+
...(isRecord(previous) ? { previous_attributes: previous } : {}),
|
|
157
157
|
},
|
|
158
158
|
};
|
|
159
159
|
}
|
|
@@ -173,11 +173,15 @@ function header(
|
|
|
173
173
|
return undefined;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
177
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178
|
+
}
|
|
179
|
+
|
|
176
180
|
function isRequest(value: unknown): value is { id: string; idempotency_key: string | null } {
|
|
177
|
-
if (
|
|
181
|
+
if (!isRecord(value)) {
|
|
178
182
|
return false;
|
|
179
183
|
}
|
|
180
|
-
const { id, idempotency_key: key } = value
|
|
184
|
+
const { id, idempotency_key: key } = value;
|
|
181
185
|
return typeof id === "string" && (key === null || typeof key === "string");
|
|
182
186
|
}
|
|
183
187
|
|