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 <zomars@me.com>
This commit is contained in:
		
							parent
							
								
									02b935bcde
								
							
						
					
					
						commit
						fa1ca5fba0
					
				
					 3 changed files with 40 additions and 1 deletions
				
			
		|  | @ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; | ||||||
| 
 | 
 | ||||||
| import { showCreateEventMessage, showTodayMessage } from "../lib"; | import { showCreateEventMessage, showTodayMessage } from "../lib"; | ||||||
| import showLinksMessage from "../lib/showLinksMessage"; | import showLinksMessage from "../lib/showLinksMessage"; | ||||||
|  | import slackVerify from "../lib/slackVerify"; | ||||||
| 
 | 
 | ||||||
| export enum SlackAppCommands { | export enum SlackAppCommands { | ||||||
|   CREATE_EVENT = "create-event", |   CREATE_EVENT = "create-event", | ||||||
|  | @ -12,7 +13,7 @@ export enum SlackAppCommands { | ||||||
| export default async function handler(req: NextApiRequest, res: NextApiResponse) { | export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||||||
|   if (req.method === "POST") { |   if (req.method === "POST") { | ||||||
|     const command = req.body.command.split("/").pop(); |     const command = req.body.command.split("/").pop(); | ||||||
| 
 |     await slackVerify(req, res); | ||||||
|     switch (command) { |     switch (command) { | ||||||
|       case SlackAppCommands.CREATE_EVENT: |       case SlackAppCommands.CREATE_EVENT: | ||||||
|         return await showCreateEventMessage(req, res); |         return await showCreateEventMessage(req, res); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { NextApiRequest, NextApiResponse } from "next"; | import { NextApiRequest, NextApiResponse } from "next"; | ||||||
| 
 | 
 | ||||||
| import createEvent from "../lib/actions/createEvent"; | import createEvent from "../lib/actions/createEvent"; | ||||||
|  | import slackVerify from "../lib/slackVerify"; | ||||||
| 
 | 
 | ||||||
| enum InteractionEvents { | enum InteractionEvents { | ||||||
|   CREATE_EVENT = "cal.event.create", |   CREATE_EVENT = "cal.event.create", | ||||||
|  | @ -8,6 +9,7 @@ enum InteractionEvents { | ||||||
| 
 | 
 | ||||||
| export default async function interactiveHandler(req: NextApiRequest, res: NextApiResponse) { | export default async function interactiveHandler(req: NextApiRequest, res: NextApiResponse) { | ||||||
|   if (req.method === "POST") { |   if (req.method === "POST") { | ||||||
|  |     await slackVerify(req, res); | ||||||
|     const payload = JSON.parse(req.body.payload); |     const payload = JSON.parse(req.body.payload); | ||||||
|     const actions = payload.view.callback_id; |     const actions = payload.view.callback_id; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										36
									
								
								packages/app-store/slackmessaging/lib/slackVerify.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								packages/app-store/slackmessaging/lib/slackVerify.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -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" }); | ||||||
|  |   } | ||||||
|  | } | ||||||
		Loading…
	
		Reference in a new issue
	
	 sean-brydon
						sean-brydon