@reqvet-sdk/sdk 2.2.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/CHANGELOG.md +23 -0
- package/README.md +179 -0
- package/SDK_REFERENCE.md +516 -0
- package/SECURITY.md +119 -0
- package/package.json +42 -0
- package/src/index.d.ts +261 -0
- package/src/index.js +444 -0
- package/src/webhooks.d.ts +14 -0
- package/src/webhooks.js +52 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function signWebhook(rawBody: string, secret: string, timestamp: string): string;
|
|
2
|
+
|
|
3
|
+
export type WebhookVerifyResult =
|
|
4
|
+
| { ok: true }
|
|
5
|
+
| { ok: false; reason: 'missing_headers' | 'invalid_timestamp' | 'stale_timestamp' | 'invalid_signature' };
|
|
6
|
+
|
|
7
|
+
export declare function verifyWebhookSignature(params: {
|
|
8
|
+
secret: string;
|
|
9
|
+
rawBody: string;
|
|
10
|
+
signature: string;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
maxSkewMs?: number;
|
|
13
|
+
now?: number;
|
|
14
|
+
}): WebhookVerifyResult;
|
package/src/webhooks.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Webhook helpers (Node.js)
|
|
2
|
+
//
|
|
3
|
+
// Used by partners to verify ReqVet webhook authenticity.
|
|
4
|
+
//
|
|
5
|
+
// Signature headers:
|
|
6
|
+
// X-ReqVet-Signature: sha256=<hex>
|
|
7
|
+
// X-ReqVet-Timestamp: <unix_ms>
|
|
8
|
+
//
|
|
9
|
+
// Expected signature:
|
|
10
|
+
// HMAC_SHA256(secret, `${timestamp}.${rawBody}`)
|
|
11
|
+
|
|
12
|
+
import crypto from 'node:crypto';
|
|
13
|
+
|
|
14
|
+
export function signWebhook(rawBody, secret, timestamp) {
|
|
15
|
+
const message = `${timestamp}.${rawBody}`;
|
|
16
|
+
const hmac = crypto.createHmac('sha256', secret).update(message).digest('hex');
|
|
17
|
+
return `sha256=${hmac}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function timingSafeEqual(a, b) {
|
|
21
|
+
const ab = Buffer.from(a);
|
|
22
|
+
const bb = Buffer.from(b);
|
|
23
|
+
if (ab.length !== bb.length) return false;
|
|
24
|
+
return crypto.timingSafeEqual(ab, bb);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Verify webhook signature + (optional) anti-replay window.
|
|
29
|
+
*/
|
|
30
|
+
export function verifyWebhookSignature({
|
|
31
|
+
secret,
|
|
32
|
+
rawBody,
|
|
33
|
+
signature,
|
|
34
|
+
timestamp,
|
|
35
|
+
maxSkewMs = 5 * 60 * 1000,
|
|
36
|
+
now = Date.now(),
|
|
37
|
+
}) {
|
|
38
|
+
if (!secret || !signature || !timestamp) {
|
|
39
|
+
return { ok: false, reason: 'missing_headers' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ts = Number(timestamp);
|
|
43
|
+
if (!Number.isFinite(ts)) return { ok: false, reason: 'invalid_timestamp' };
|
|
44
|
+
if (Math.abs(now - ts) > maxSkewMs) return { ok: false, reason: 'stale_timestamp' };
|
|
45
|
+
|
|
46
|
+
const expected = signWebhook(rawBody, secret, String(timestamp));
|
|
47
|
+
if (!timingSafeEqual(String(signature), expected)) {
|
|
48
|
+
return { ok: false, reason: 'invalid_signature' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { ok: true };
|
|
52
|
+
}
|