Repository: octokit/webhooks.js
Pull request #587 (2021-06-17 19:56:45)

fix: bump `@octokit/webhooks-methods` to `^2.0.0`
follow up to https://github.com/octokit/webhooks-methods.js/pull/21



Patch:
diff --git a/package-lock.json b/package-lock.json
index c9f2245..789bdfc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1644,9 +1644,9 @@
       }
     },
     "@octokit/webhooks-methods": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-1.0.0.tgz",
-      "integrity": "sha512-pVceMQcj9SZ5p2RkemL0TuuPdGULNQj9F3Pq1cNM1xH+Kst1VNt0dj3PEGZRZV473njrDnYdi/OG4wWY9TLbbA=="
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz",
+      "integrity": "sha512-35cfQ4YWlnZnmZKmIxlGPUPLtbkF8lr/A/1Sk1eC0ddLMwQN06dOuLc+dI3YLQS+T+MoNt3DIQ0NynwgKPilig=="
     },
     "@octokit/webhooks-schemas": {
       "version": "4.0.0",
@@ -7409,12 +7409,12 @@
       }
     },
     "npm": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/npm/-/npm-7.17.0.tgz",
-      "integrity": "sha512-yNzj4vQellvUGiBM/SzhfT89EV0vb7iILjTehSydTY/IgK2Vjk7/7J8WNJ2ysqcgfLY21ptI/j7uknt15IbbKQ==",
+      "version": "7.18.1",
+      "resolved": "https://registry.npmjs.org/npm/-/npm-7.18.1.tgz",
+      "integrity": "sha512-fu7rMtc4ZaDnFlZsiLhqwWWs6KixOcBGiBLoJH+AbuaznwtT8mYQnq5nGNN14Jib+E9OjkjNRgg+X45Ia9xtKQ==",
       "dev": true,
       "requires": {
-        "@npmcli/arborist": "^2.6.1",
+        "@npmcli/arborist": "^2.6.3",
         "@npmcli/ci-detect": "^1.2.0",
         "@npmcli/config": "^2.2.0",
         "@npmcli/run-script": "^1.8.5",
@@ -7439,7 +7439,7 @@
         "leven": "^3.1.0",
         "libnpmaccess": "^4.0.2",
         "libnpmdiff": "^2.0.4",
-        "libnpmexec": "^1.2.0",
+        "libnpmexec": "^2.0.0",
         "libnpmfund": "^1.1.0",
         "libnpmhook": "^6.0.2",
         "libnpmorg": "^2.0.2",
@@ -7447,8 +7447,8 @@
         "libnpmpublish": "^4.0.1",
         "libnpmsearch": "^3.1.1",
         "libnpmteam": "^2.0.3",
-        "libnpmversion": "^1.2.0",
-        "make-fetch-happen": "^9.0.1",
+        "libnpmversion": "^1.2.1",
+        "make-fetch-happen": "^9.0.3",
         "minipass": "^3.1.3",
         "minipass-pipeline": "^1.2.4",
         "mkdirp": "^1.0.4",
@@ -7457,7 +7457,7 @@
         "node-gyp": "^7.1.2",
         "nopt": "^5.0.0",
         "npm-audit-report": "^2.1.5",
-        "npm-package-arg": "^8.1.4",
+        "npm-package-arg": "^8.1.5",
         "npm-pick-manifest": "^6.1.1",
         "npm-profile": "^5.0.3",
         "npm-registry-fetch": "^11.0.0",
@@ -7484,7 +7484,7 @@
       },
       "dependencies": {
         "@npmcli/arborist": {
-          "version": "2.6.2",
+          "version": "2.6.3",
           "bundled": true,
           "dev": true,
           "requires": {
@@ -7507,6 +7507,7 @@
             "npm-registry-fetch": "^11.0.0",
             "pacote": "^11.2.6",
             "parse-conflict-json": "^1.1.1",
+            "proc-log": "^1.0.0",
             "promise-all-reject-late": "^1.0.0",
             "promise-call-limit": "^1.0.1",
             "read-package-json-fast": "^2.0.2",
@@ -8471,13 +8472,13 @@
             "binary-extensions": "^2.2.0",
             "diff": "^5.0.0",
             "minimatch": "^3.0.4",
-            "npm-package-arg": "^8.1.1",
-            "pacote": "^11.3.0",
+            "npm-package-arg": "^8.1.4",
+            "pacote": "^11.3.4",
             "tar": "^6.1.0"
           }
         },
         "libnpmexec": {
-          "version": "1.2.0",
+          "version": "2.0.0",
           "bundled": true,
           "dev": true,
           "requires": {
@@ -8560,7 +8561,7 @@
           }
         },
         "libnpmversion": {
-          "version": "1.2.0",
+          "version": "1.2.1",
           "bundled": true,
           "dev": true,
           "requires": {
@@ -8580,7 +8581,7 @@
           }
         },
         "make-fetch-happen": {
-          "version": "9.0.2",
+          "version": "9.0.3",
           "bundled": true,
           "dev": true,
           "requires": {
@@ -8788,7 +8789,7 @@
           "dev": true
         },
         "npm-package-arg": {
-          "version": "8.1.4",
+          "version": "8.1.5",
           "bundled": true,
           "dev": true,
           "requires": {
@@ -11691,9 +11692,9 @@
       }
     },
     "typescript": {
-      "version": "4.3.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.3.tgz",
-      "integrity": "sha512-rUvLW0WtF7PF2b9yenwWUi9Da9euvDRhmH7BLyBG4DCFfOJ850LGNknmRpp8Z8kXNUPObdZQEfKOiHtXuQHHKA==",
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz",
+      "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==",
       "dev": true
     },
     "uglify-js": {
diff --git a/package.json b/package.json
index 73ff339..874e31f 100644
--- a/package.json
+++ b/package.json
@@ -10,8 +10,8 @@
     "build": "pika build",
     "coverage": "jest --coverage && open coverage/lcov-report/index.html",
     "generate-types": "ts-node --transpile-only scripts/generate-types.ts",
-    "lint": "prettier --check 'src/**/*.{ts,json}' 'scripts/**/*' 'test/**/*' README.md package.json",
-    "lint:fix": "prettier --write 'src/**/*.{ts,json}' 'scripts/**/*' 'test/**/*' README.md package.json",
+    "lint": "prettier --check 'src/**/*.{ts,json}' 'scripts/**/*' 'test/**/*.ts' README.md package.json",
+    "lint:fix": "prettier --write 'src/**/*.{ts,json}' 'scripts/**/*' 'test/**/*.ts' README.md package.json",
     "pretest": "npm run -s lint",
     "test": "jest --coverage",
     "validate:ts": "tsc --noEmit --noImplicitAny --target es2020 --esModuleInterop --moduleResolution node test/typescript-validate.ts"
@@ -19,7 +19,7 @@
   "prettier": {},
   "dependencies": {
     "@octokit/request-error": "^2.0.2",
-    "@octokit/webhooks-methods": "^1.0.0",
+    "@octokit/webhooks-methods": "^2.0.0",
     "@octokit/webhooks-types": "4.0.0",
     "aggregate-error": "^3.1.0"
   },
diff --git a/src/index.ts b/src/index.ts
index 057a947..efb527a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,7 @@
-import { sign, verify } from "@octokit/webhooks-methods";
-
 import { createLogger } from "./createLogger";
 import { createEventHandler } from "./event-handler/index";
+import { sign } from "./sign";
+import { verify } from "./verify";
 import { verifyAndReceive } from "./verify-and-receive";
 import {
   EmitterWebhookEvent,
diff --git a/src/sign.ts b/src/sign.ts
new file mode 100644
index 0000000..69efeee
--- /dev/null
+++ b/src/sign.ts
@@ -0,0 +1,13 @@
+import { sign as signMethod } from "@octokit/webhooks-methods";
+
+import { toNormalizedJsonString } from "./to-normalized-json-string";
+
+export async function sign(
+  secret: string,
+  payload: string | object
+): Promise<any> {
+  return signMethod(
+    secret,
+    typeof payload === "string" ? payload : toNormalizedJsonString(payload)
+  );
+}
diff --git a/src/to-normalized-json-string.ts b/src/to-normalized-json-string.ts
new file mode 100644
index 0000000..5dc5169
--- /dev/null
+++ b/src/to-normalized-json-string.ts
@@ -0,0 +1,9 @@
+/**
+ * GitHub sends its JSON with an indentation of 2 spaces and a line break at the end
+ */
+export function toNormalizedJsonString(payload: object) {
+  const payloadString = JSON.stringify(payload, null, 2) + "\n";
+  return payloadString.replace(/[^\\]\\u[\da-f]{4}/g, (s) => {
+    return s.substr(0, 3) + s.substr(3).toUpperCase();
+  });
+}
diff --git a/src/verify-and-receive.ts b/src/verify-and-receive.ts
index 6b6ac57..06a5944 100644
--- a/src/verify-and-receive.ts
+++ b/src/verify-and-receive.ts
@@ -1,5 +1,6 @@
 import { verify } from "@octokit/webhooks-methods";
 
+import { toNormalizedJsonString } from "./to-normalized-json-string";
 import {
   EmitterWebhookEventWithStringPayloadAndSignature,
   EmitterWebhookEventWithSignature,
@@ -15,7 +16,9 @@ export async function verifyAndReceive(
   // verify will validate that the secret is not undefined
   const matchesSignature = await verify(
     state.secret,
-    event.payload,
+    typeof event.payload === "object"
+      ? toNormalizedJsonString(event.payload)
+      : event.payload,
     event.signature
   );
 
diff --git a/src/verify.ts b/src/verify.ts
new file mode 100644
index 0000000..eaf5cfa
--- /dev/null
+++ b/src/verify.ts
@@ -0,0 +1,15 @@
+import { verify as verifyMethod } from "@octokit/webhooks-methods";
+
+import { toNormalizedJsonString } from "./to-normalized-json-string";
+
+export async function verify(
+  secret: string,
+  payload: string | object,
+  signature: string
+): Promise<any> {
+  return verifyMethod(
+    secret,
+    typeof payload === "string" ? payload : toNormalizedJsonString(payload),
+    signature
+  );
+}


Test patch:
diff --git a/test/fixtures/push-payload.json b/test/fixtures/push-payload.json
index 2f9b5eb..df3d1d1 100644
--- a/test/fixtures/push-payload.json
+++ b/test/fixtures/push-payload.json
@@ -27,7 +27,9 @@
       },
       "added": [],
       "removed": [],
-      "modified": ["README.md"]
+      "modified": [
+        "README.md"
+      ]
     }
   ],
   "head_commit": {
@@ -49,7 +51,9 @@
     },
     "added": [],
     "removed": [],
-    "modified": ["README.md"]
+    "modified": [
+      "README.md"
+    ]
   },
   "repository": {
     "id": 35129377,
diff --git a/test/integration/node-middleware.test.ts b/test/integration/node-middleware.test.ts
index dd458cd..990cf5f 100644
--- a/test/integration/node-middleware.test.ts
+++ b/test/integration/node-middleware.test.ts
@@ -1,4 +1,5 @@
 import { createServer } from "http";
+import { readFileSync } from "fs";
 
 import fetch from "node-fetch";
 import { sign } from "@octokit/webhooks-methods";
@@ -7,15 +8,18 @@ import { sign } from "@octokit/webhooks-methods";
 const express = require("express");
 
 import { Webhooks, createNodeMiddleware } from "../../src";
-import { pushEventPayload } from "../fixtures";
 
+const pushEventPayload = readFileSync(
+  "test/fixtures/push-payload.json",
+  "utf-8"
+);
 let signatureSha256: string;
 
 describe("createNodeMiddleware(webhooks)", () => {
   beforeAll(async () => {
     signatureSha256 = await sign(
       { secret: "mySecret", algorithm: "sha256" },
-      JSON.stringify(pushEventPayload)
+      pushEventPayload
     );
   });
 
@@ -48,7 +52,7 @@ describe("createNodeMiddleware(webhooks)", () => {
           "X-GitHub-Event": "push",
           "X-Hub-Signature-256": signatureSha256,
         },
-        body: JSON.stringify(pushEventPayload),
+        body: pushEventPayload,
       }
     );
 
@@ -92,7 +96,7 @@ describe("createNodeMiddleware(webhooks)", () => {
           "X-GitHub-Event": "push",
           "X-Hub-Signature-256": signatureSha256,
         },
-        body: JSON.stringify(pushEventPayload),
+        body: pushEventPayload,
       }
     );
 
@@ -256,7 +260,7 @@ describe("createNodeMiddleware(webhooks)", () => {
           "X-GitHub-Event": "push",
           "X-Hub-Signature-256": signatureSha256,
         },
-        body: JSON.stringify(pushEventPayload),
+        body: pushEventPayload,
       }
     );
 
@@ -292,7 +296,7 @@ describe("createNodeMiddleware(webhooks)", () => {
           "X-GitHub-Event": "push",
           "X-Hub-Signature-256": signatureSha256,
         },
-        body: JSON.stringify(pushEventPayload),
+        body: pushEventPayload,
       }
     );
 
@@ -327,7 +331,7 @@ describe("createNodeMiddleware(webhooks)", () => {
           "X-GitHub-Event": "push",
           "X-Hub-Signature-256": signatureSha256,
         },
-        body: JSON.stringify(pushEventPayload),
+        body: pushEventPayload,
       }
     );
 
@@ -352,7 +356,7 @@ describe("createNodeMiddleware(webhooks)", () => {
 
     const response = await fetch(`http://localhost:${port}/test`, {
       method: "POST",
-      body: JSON.stringify(pushEventPayload),
+      body: pushEventPayload,
     });
 
     await expect(response.text()).resolves.toBe("Dafuq");
@@ -376,7 +380,7 @@ describe("createNodeMiddleware(webhooks)", () => {
 
     const response = await fetch(`http://localhost:${port}/test`, {
       method: "POST",
-      body: JSON.stringify(pushEventPayload),
+      body: pushEventPayload,
     });
 
     await expect(response.text()).resolves.toContain("Cannot POST /test");
@@ -384,7 +388,7 @@ describe("createNodeMiddleware(webhooks)", () => {
 
     const responseForFoo = await fetch(`http://localhost:${port}/foo`, {
       method: "POST",
-      body: JSON.stringify(pushEventPayload),
+      body: pushEventPayload,
     });
 
     await expect(responseForFoo.text()).resolves.toContain("ok\n");
@@ -415,7 +419,7 @@ describe("createNodeMiddleware(webhooks)", () => {
         "X-GitHub-Event": "push",
         "X-Hub-Signature-256": signatureSha256,
       },
-      body: JSON.stringify(pushEventPayload),
+      body: pushEventPayload,
     });
 
     await expect(response.text()).resolves.toBe("ok\n");
@@ -446,7 +450,7 @@ describe("createNodeMiddleware(webhooks)", () => {
         "X-GitHub-Event": "push",
         "X-Hub-Signature-256": signatureSha256,
       },
-      body: JSON.stringify(pushEventPayload),
+      body: pushEventPayload,
     });
 
     await expect(response.text()).resolves.toBe("ok\n");
diff --git a/test/integration/webhooks.test.ts b/test/integration/webhooks.test.ts
index 295eb3a..97337f1 100644
--- a/test/integration/webhooks.test.ts
+++ b/test/integration/webhooks.test.ts
@@ -3,6 +3,7 @@ import { readFileSync } from "fs";
 import { sign } from "@octokit/webhooks-methods";
 
 import { Webhooks, EmitterWebhookEvent } from "../../src";
+import { toNormalizedJsonString } from "../../src/to-normalized-json-string";
 
 const pushEventPayloadString = readFileSync(
   "test/fixtures/push-payload.json",
@@ -17,6 +18,44 @@ describe("Webhooks", () => {
     );
   });
 
+  test("webhooks.sign(payload) with object payload", async () => {
+    const secret = "mysecret";
+    const webhooks = new Webhooks({ secret });
+
+    await webhooks.sign(JSON.parse(pushEventPayloadString));
+  });
+
+  test("webhooks.sign(payload) with string payload", async () => {
+    const secret = "mysecret";
+    const webhooks = new Webhooks({ secret });
+
+    await webhooks.sign(pushEventPayloadString);
+  });
+
+  test("webhooks.verify(payload, signature) with object payload", async () => {
+    const secret = "mysecret";
+    const webhooks = new Webhooks({ secret });
+
+    await webhooks.verify(
+      JSON.parse(pushEventPayloadString),
+      await sign({ secret, algorithm: "sha256" }, pushEventPayloadString)
+    );
+  });
+
+  test("webhooks.verify(payload, signature) with object payload containing special characters", async () => {
+    const secret = "mysecret";
+    const webhooks = new Webhooks({ secret });
+
+    const payload = {
+      foo: "Foo\n\u001b[34mbar: ♥♥♥♥♥♥♥♥\nthis-is-lost\u001b[0m\u001b[2K",
+    };
+
+    await webhooks.verify(
+      payload,
+      await sign(secret, toNormalizedJsonString(payload))
+    );
+  });
+
   test("webhooks.verify(payload, signature) with string payload", async () => {
     const secret = "mysecret";
     const webhooks = new Webhooks({ secret });