rootspring a révisé ce gist . Aller à la révision
1 file changed, 1 insertion, 7 deletions
webhooks.md
@@ -106,10 +106,4 @@ To parse webhooks, here is the algorithm you should/must follow: | |||
106 | 106 | error: true, | |
107 | 107 | }); | |
108 | 108 | return; | |
109 | - | ``` | |
110 | - | ||
111 | - | ## HTML Sanitizer | |
112 | - | ||
113 | - | We use a custom server to parse markdown (using ``pulldown-cmark``) and sanitize HTML (using ``ammonia``). This is to prevent XSS attacks and other malicious code from being injected into our site. | |
114 | - | ||
115 | - | The URL for HTML Sanitizer is ``https://hs.infinitybots.gg``. To use this for previews etc, simply POST the raw MD/HTML to this URL and it will return the sanitized HTML. | |
109 | + | ``` |
rootspring a révisé ce gist . Aller à la révision
1 file changed, 115 insertions
webhooks.md(fichier créé)
@@ -0,0 +1,115 @@ | |||
1 | + | # Parsing webhooks | |
2 | + | ||
3 | + | To parse webhooks, here is the algorithm you should/must follow: | |
4 | + | ||
5 | + | **Examples are provided in JS as of right now** | |
6 | + | ||
7 | + | - Check the protocol version: | |
8 | + | - The current protocol version is `splashtail` | |
9 | + | - Check the `X-Webhook-Protocol` header and ensure that it is equal to the current protocol version | |
10 | + | ||
11 | + | ```js | |
12 | + | if (req.headers["x-webhook-protocol"] != supportedProtocol) { | |
13 | + | reply.status(403).send({ | |
14 | + | message: "Invalid protocol version!", | |
15 | + | error: true, | |
16 | + | }); | |
17 | + | return; | |
18 | + | } | |
19 | + | ``` | |
20 | + | ||
21 | + | - A nonce is used to randomize the signature for retries. Ensure a nonce exists by checking the header's existence: | |
22 | + | ||
23 | + | ```js | |
24 | + | if (!req.headers["x-webhook-nonce"]) { | |
25 | + | reply.status(403).send({ | |
26 | + | message: "No nonce provided?", | |
27 | + | error: true, | |
28 | + | }); | |
29 | + | return; | |
30 | + | } | |
31 | + | ``` | |
32 | + | ||
33 | + | - Next calculate the expected signature | |
34 | + | - To do so, you must first get the body of the request | |
35 | + | - Then use HMAC-SHA512 with the webhook secret as key and the body as the request body to get the ``signedBody``. Note that the format/digest should be ``hex`` | |
36 | + | - Then use HMAC-SHA512 with the nonce as the key and the signed body as the message to get the expected signature. Note that the format/digest should be ``hex`` | |
37 | + | ||
38 | + | ```js | |
39 | + | let body: string = req.body; | |
40 | + | ||
41 | + | if (!body) { | |
42 | + | reply.status(400).send({ | |
43 | + | message: "No request body provided?", | |
44 | + | error: true, | |
45 | + | }); | |
46 | + | return; | |
47 | + | } | |
48 | + | ||
49 | + | // Create hmac 512 hash | |
50 | + | let signedBody = crypto | |
51 | + | .createHmac("sha512", webhookSecret) | |
52 | + | .update(body) | |
53 | + | .digest("hex"); | |
54 | + | ||
55 | + | // Create the actual signature using x-webhook-nonce by performing a second hmac | |
56 | + | let nonce = req.headers["x-webhook-nonce"].toString(); | |
57 | + | let expectedTok = crypto | |
58 | + | .createHmac("sha512", nonce) | |
59 | + | .update(signedBody) | |
60 | + | .digest("hex"); | |
61 | + | ``` | |
62 | + | ||
63 | + | - Compare this value with the ``X-Webhook-Signature`` header | |
64 | + | - If they are equal, the request is valid and you can continue processing it | |
65 | + | - If they are not equal, the request is invalid and you should return a 403 status code | |
66 | + | ||
67 | + | ||
68 | + | ||
69 | + | ```js | |
70 | + | if (req.headers["x-webhook-signature"] != expectedTok) { | |
71 | + | console.log( | |
72 | + | `Expected: ${expectedTok} Got: ${req.headers["x-webhook-signature"]}` | |
73 | + | ); | |
74 | + | reply.status(403).send({ | |
75 | + | message: "Invalid signature", | |
76 | + | }); | |
77 | + | return; | |
78 | + | } | |
79 | + | ``` | |
80 | + | ||
81 | + | - Next decrypt the request body. This is an additional security to prevent sensitive information from being leaked | |
82 | + | - First hash the concatenation of the webhook secret and the nonce using SHA256 | |
83 | + | - Then read the body as a hex string and decrypt it using AES-256-GCM with the hashed secret as the key | |
84 | + | ||
85 | + | ```js | |
86 | + | // sha256 on key | |
87 | + | let hashedKey = crypto | |
88 | + | .createHash("sha256") | |
89 | + | .update(webhookSecret + nonce) | |
90 | + | .digest(); | |
91 | + | ||
92 | + | let enc = Buffer.from(body, "hex"); | |
93 | + | const tag = enc.subarray(enc.length - tagLength, enc.length); | |
94 | + | const iv = enc.subarray(0, ivLength); | |
95 | + | const toDecrypt = enc.subarray(ivLength, enc.length - tag.length); | |
96 | + | const decipher = crypto.createDecipheriv("aes-256-gcm", hashedKey, iv); | |
97 | + | decipher.setAuthTag(tag); | |
98 | + | const res = Buffer.concat([decipher.update(toDecrypt), decipher.final()]); | |
99 | + | ||
100 | + | // Parse the decrypted body | |
101 | + | let data = JSON.parse(res.toString("utf-8")); | |
102 | + | ||
103 | + | if (data.created_at == undefined) { | |
104 | + | reply.status(400).send({ | |
105 | + | message: "Invalid body", | |
106 | + | error: true, | |
107 | + | }); | |
108 | + | return; | |
109 | + | ``` | |
110 | + | ||
111 | + | ## HTML Sanitizer | |
112 | + | ||
113 | + | We use a custom server to parse markdown (using ``pulldown-cmark``) and sanitize HTML (using ``ammonia``). This is to prevent XSS attacks and other malicious code from being injected into our site. | |
114 | + | ||
115 | + | The URL for HTML Sanitizer is ``https://hs.infinitybots.gg``. To use this for previews etc, simply POST the raw MD/HTML to this URL and it will return the sanitized HTML. |