From fa1ca5fba00a5fde7b7e45c561f0ee1e8789c3fa Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Wed, 4 May 2022 00:40:01 +0100 Subject: [PATCH] Slack Signature Verification (#2667) * Slack Verify * Adding await * Update slackVerify.ts Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: zomars --- .../slackmessaging/api/commandHandler.ts | 3 +- .../slackmessaging/api/interactiveHandler.ts | 2 ++ .../slackmessaging/lib/slackVerify.ts | 36 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 packages/app-store/slackmessaging/lib/slackVerify.ts diff --git a/packages/app-store/slackmessaging/api/commandHandler.ts b/packages/app-store/slackmessaging/api/commandHandler.ts index 57845cb3..6b78eecd 100644 --- a/packages/app-store/slackmessaging/api/commandHandler.ts +++ b/packages/app-store/slackmessaging/api/commandHandler.ts @@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { showCreateEventMessage, showTodayMessage } from "../lib"; import showLinksMessage from "../lib/showLinksMessage"; +import slackVerify from "../lib/slackVerify"; export enum SlackAppCommands { CREATE_EVENT = "create-event", @@ -12,7 +13,7 @@ export enum SlackAppCommands { export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === "POST") { const command = req.body.command.split("/").pop(); - + await slackVerify(req, res); switch (command) { case SlackAppCommands.CREATE_EVENT: return await showCreateEventMessage(req, res); diff --git a/packages/app-store/slackmessaging/api/interactiveHandler.ts b/packages/app-store/slackmessaging/api/interactiveHandler.ts index 290c4774..25c07b16 100644 --- a/packages/app-store/slackmessaging/api/interactiveHandler.ts +++ b/packages/app-store/slackmessaging/api/interactiveHandler.ts @@ -1,6 +1,7 @@ import { NextApiRequest, NextApiResponse } from "next"; import createEvent from "../lib/actions/createEvent"; +import slackVerify from "../lib/slackVerify"; enum InteractionEvents { CREATE_EVENT = "cal.event.create", @@ -8,6 +9,7 @@ enum InteractionEvents { export default async function interactiveHandler(req: NextApiRequest, res: NextApiResponse) { if (req.method === "POST") { + await slackVerify(req, res); const payload = JSON.parse(req.body.payload); const actions = payload.view.callback_id; diff --git a/packages/app-store/slackmessaging/lib/slackVerify.ts b/packages/app-store/slackmessaging/lib/slackVerify.ts new file mode 100644 index 00000000..fc43b502 --- /dev/null +++ b/packages/app-store/slackmessaging/lib/slackVerify.ts @@ -0,0 +1,36 @@ +import { createHmac } from "crypto"; +import dayjs from "dayjs"; +import { NextApiRequest, NextApiResponse } from "next"; +import { stringify } from "querystring"; + +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; + +let signingSecret = ""; + +export default async function slackVerify(req: NextApiRequest, res: NextApiResponse) { + const body = req.body; + const timeStamp = req.headers["x-slack-request-timestamp"] as string; // Always returns a string and not a string[] + const slackSignature = req.headers["x-slack-signature"] as string; + const currentTime = dayjs().unix(); + let { signing_secret } = await getAppKeysFromSlug("slack"); + if (typeof signing_secret === "string") signingSecret = signing_secret; + + if (!timeStamp) { + return res.status(400).json({ message: "Missing X-Slack-Request-Timestamp header" }); + } + + if (!signingSecret) { + return res.status(400).json({ message: "Missing Slack's signing_secret" }); + } + + if (Math.abs(currentTime - parseInt(timeStamp)) > 60 * 5) { + return res.status(400).json({ message: "Request is too old" }); + } + + const signature_base = `v0:${timeStamp}:${stringify(body)}`; + const signed_sig = "v0=" + createHmac("sha256", signingSecret).update(signature_base).digest("hex"); + + if (signed_sig !== slackSignature) { + return res.status(400).json({ message: "Invalid signature" }); + } +}