From 66f31d5dee5e5618a6124fdd97a0cc567f3cce3d Mon Sep 17 00:00:00 2001 From: Kenichiro NOGI Date: Sat, 5 Sep 2026 14:37:44 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20LINEWORKS=20Webhook=E7=BD=B2=E5=90=8D?= =?UTF-8?q?=E6=A4=9C=E8=A8=BC=E3=83=AD=E3=82=B8=E3=83=83=E3=82=AF=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/lib/signatureVerify.js | 24 +++++++++++++++++ .../test/signatureVerify.test.js | 27 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 NodeSrv/apps/healthcheck-survey-bot/src/lib/signatureVerify.js create mode 100644 NodeSrv/apps/healthcheck-survey-bot/test/signatureVerify.test.js diff --git a/NodeSrv/apps/healthcheck-survey-bot/src/lib/signatureVerify.js b/NodeSrv/apps/healthcheck-survey-bot/src/lib/signatureVerify.js new file mode 100644 index 00000000..5fc47d3b --- /dev/null +++ b/NodeSrv/apps/healthcheck-survey-bot/src/lib/signatureVerify.js @@ -0,0 +1,24 @@ +const crypto = require("node:crypto"); + +function normalizeSignature(value) { + return String(value || "").trim().replace(/^sha256=/i, ""); +} + +function safeEqual(a, b) { + const ab = Buffer.from(String(a), "utf8"); + const bb = Buffer.from(String(b), "utf8"); + if (ab.length !== bb.length) return false; + return crypto.timingSafeEqual(ab, bb); +} + +function verifySignature(rawBody, headerSignature, botSecret) { + const headerSig = normalizeSignature(headerSignature); + if (!headerSig || !botSecret) return false; + + const payload = Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(String(rawBody), "utf8"); + const expected = crypto.createHmac("sha256", botSecret).update(payload).digest("base64"); + + return safeEqual(headerSig, expected); +} + +module.exports = { verifySignature }; diff --git a/NodeSrv/apps/healthcheck-survey-bot/test/signatureVerify.test.js b/NodeSrv/apps/healthcheck-survey-bot/test/signatureVerify.test.js new file mode 100644 index 00000000..bc8776ab --- /dev/null +++ b/NodeSrv/apps/healthcheck-survey-bot/test/signatureVerify.test.js @@ -0,0 +1,27 @@ +// test/signatureVerify.test.js +const { test } = require("node:test"); +const assert = require("node:assert"); +const crypto = require("node:crypto"); +const { verifySignature } = require("../src/lib/signatureVerify"); + +test("正しい署名はtrueを返す", () => { + const secret = "test-secret"; + const body = JSON.stringify({ hello: "world" }); + const signature = crypto.createHmac("sha256", secret).update(body).digest("base64"); + assert.strictEqual(verifySignature(body, signature, secret), true); +}); + +test("sha256=プレフィックス付き署名も検証できる", () => { + const secret = "test-secret"; + const body = JSON.stringify({ hello: "world" }); + const signature = crypto.createHmac("sha256", secret).update(body).digest("base64"); + assert.strictEqual(verifySignature(body, `sha256=${signature}`, secret), true); +}); + +test("不正な署名はfalseを返す", () => { + assert.strictEqual(verifySignature("body", "invalid-signature", "secret"), false); +}); + +test("署名ヘッダーが空ならfalseを返す", () => { + assert.strictEqual(verifySignature("body", "", "secret"), false); +});