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" }); + } +}