@notifykit/sdk 1.1.0 → 1.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/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/types.d.ts +0 -8
- package/dist/webhooks.d.ts +40 -0
- package/dist/webhooks.js +102 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -14,8 +14,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.NotifyKitClient = void 0;
|
|
17
|
+
exports.verifyWebhookSignature = exports.NotifyKitClient = void 0;
|
|
18
18
|
var client_1 = require("./client");
|
|
19
19
|
Object.defineProperty(exports, "NotifyKitClient", { enumerable: true, get: function () { return client_1.NotifyKitClient; } });
|
|
20
20
|
__exportStar(require("./types"), exports);
|
|
21
21
|
__exportStar(require("./errors"), exports);
|
|
22
|
+
var webhooks_1 = require("./webhooks");
|
|
23
|
+
Object.defineProperty(exports, "verifyWebhookSignature", { enumerable: true, get: function () { return webhooks_1.verifyWebhookSignature; } });
|
package/dist/types.d.ts
CHANGED
|
@@ -16,15 +16,7 @@ export interface SendEmailOptions {
|
|
|
16
16
|
from?: string;
|
|
17
17
|
priority?: 1 | 5 | 10;
|
|
18
18
|
idempotencyKey?: string;
|
|
19
|
-
/**
|
|
20
|
-
* Force this email through a specific configured provider (paid plans only).
|
|
21
|
-
* If unset, the customer's priority order with full failover applies.
|
|
22
|
-
*/
|
|
23
19
|
provider?: EmailProvider;
|
|
24
|
-
/**
|
|
25
|
-
* Fallback provider to try if `provider` fails. Ignored unless `provider`
|
|
26
|
-
* is set. Other configured providers are not tried.
|
|
27
|
-
*/
|
|
28
20
|
fallback?: EmailProvider;
|
|
29
21
|
}
|
|
30
22
|
export interface SendWebhookOptions {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface VerifyWebhookSignatureOptions {
|
|
2
|
+
/** Raw request body as a string — do NOT pass a parsed object. */
|
|
3
|
+
payload: string;
|
|
4
|
+
/** Value of the X-Webhook-Timestamp header. */
|
|
5
|
+
timestamp: string;
|
|
6
|
+
/** Value of the X-Webhook-Signature header (format: t=<ts>,v1=<hex>). */
|
|
7
|
+
signature: string;
|
|
8
|
+
/** Plaintext webhook signing secret from your NotifyKit dashboard. */
|
|
9
|
+
secret: string;
|
|
10
|
+
/**
|
|
11
|
+
* Maximum age of the request in seconds before it is rejected as a replay.
|
|
12
|
+
* Defaults to 300 (5 minutes).
|
|
13
|
+
*/
|
|
14
|
+
tolerance?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Verify an incoming webhook signature from NotifyKit.
|
|
18
|
+
*
|
|
19
|
+
* NotifyKit signs outgoing webhook requests with HMAC-SHA256 when a signing
|
|
20
|
+
* secret is configured. Call this on your receiving endpoint to confirm the
|
|
21
|
+
* request is genuine and has not been replayed.
|
|
22
|
+
*
|
|
23
|
+
* @returns `true` if the signature is valid and the request is within the
|
|
24
|
+
* tolerance window, `false` otherwise.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* app.post('/webhook', (req, res) => {
|
|
29
|
+
* const valid = verifyWebhookSignature({
|
|
30
|
+
* payload: req.rawBody, // raw string, not parsed JSON
|
|
31
|
+
* timestamp: req.headers['x-webhook-timestamp'],
|
|
32
|
+
* signature: req.headers['x-webhook-signature'],
|
|
33
|
+
* secret: process.env.NOTIFYKIT_WEBHOOK_SECRET,
|
|
34
|
+
* });
|
|
35
|
+
* if (!valid) return res.status(401).send('Invalid signature');
|
|
36
|
+
* // ...
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function verifyWebhookSignature(options: VerifyWebhookSignatureOptions): boolean;
|
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
/**
|
|
39
|
+
* Verify an incoming webhook signature from NotifyKit.
|
|
40
|
+
*
|
|
41
|
+
* NotifyKit signs outgoing webhook requests with HMAC-SHA256 when a signing
|
|
42
|
+
* secret is configured. Call this on your receiving endpoint to confirm the
|
|
43
|
+
* request is genuine and has not been replayed.
|
|
44
|
+
*
|
|
45
|
+
* @returns `true` if the signature is valid and the request is within the
|
|
46
|
+
* tolerance window, `false` otherwise.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* app.post('/webhook', (req, res) => {
|
|
51
|
+
* const valid = verifyWebhookSignature({
|
|
52
|
+
* payload: req.rawBody, // raw string, not parsed JSON
|
|
53
|
+
* timestamp: req.headers['x-webhook-timestamp'],
|
|
54
|
+
* signature: req.headers['x-webhook-signature'],
|
|
55
|
+
* secret: process.env.NOTIFYKIT_WEBHOOK_SECRET,
|
|
56
|
+
* });
|
|
57
|
+
* if (!valid) return res.status(401).send('Invalid signature');
|
|
58
|
+
* // ...
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
function verifyWebhookSignature(options) {
|
|
63
|
+
const { payload, timestamp, signature, secret, tolerance = 300 } = options;
|
|
64
|
+
let v1;
|
|
65
|
+
let headerTs;
|
|
66
|
+
for (const part of signature.split(",")) {
|
|
67
|
+
const eq = part.indexOf("=");
|
|
68
|
+
if (eq === -1)
|
|
69
|
+
continue;
|
|
70
|
+
const key = part.slice(0, eq).trim();
|
|
71
|
+
const val = part.slice(eq + 1).trim();
|
|
72
|
+
if (key === "v1")
|
|
73
|
+
v1 = val;
|
|
74
|
+
if (key === "t")
|
|
75
|
+
headerTs = val;
|
|
76
|
+
}
|
|
77
|
+
if (!v1 || !headerTs)
|
|
78
|
+
return false;
|
|
79
|
+
if (headerTs !== timestamp)
|
|
80
|
+
return false;
|
|
81
|
+
const ts = parseInt(timestamp, 10);
|
|
82
|
+
if (!Number.isFinite(ts))
|
|
83
|
+
return false;
|
|
84
|
+
const age = Math.floor(Date.now() / 1000) - ts;
|
|
85
|
+
if (age < 0 || age > tolerance)
|
|
86
|
+
return false;
|
|
87
|
+
const signed = `${timestamp}.${payload}`;
|
|
88
|
+
const expected = crypto
|
|
89
|
+
.createHmac("sha256", secret)
|
|
90
|
+
.update(signed)
|
|
91
|
+
.digest("hex");
|
|
92
|
+
try {
|
|
93
|
+
const expectedBuf = Buffer.from(expected, "hex");
|
|
94
|
+
const actualBuf = Buffer.from(v1, "hex");
|
|
95
|
+
if (expectedBuf.length !== actualBuf.length)
|
|
96
|
+
return false;
|
|
97
|
+
return crypto.timingSafeEqual(expectedBuf, actualBuf);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|