From 6b8863ec92f134c37dddeb3f404063d65e541da9 Mon Sep 17 00:00:00 2001 From: Chris Pyle Date: Thu, 12 Dec 2024 20:02:05 -0500 Subject: [PATCH] #3044 challenge handshake and event verification --- src/backend/index.ts | 2 + .../src/controllers/slack.controllers.ts | 42 +++++++++++++++++++ src/backend/src/routes/slack.routes.ts | 8 ++++ src/backend/src/services/slack.services.ts | 6 +++ src/backend/src/utils/auth.utils.ts | 9 ++-- 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 src/backend/src/controllers/slack.controllers.ts create mode 100644 src/backend/src/routes/slack.routes.ts create mode 100644 src/backend/src/services/slack.services.ts diff --git a/src/backend/index.ts b/src/backend/index.ts index 97d683ec5f..79a1b99f37 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -17,6 +17,7 @@ import workPackageTemplatesRouter from './src/routes/work-package-templates.rout import carsRouter from './src/routes/cars.routes'; import organizationRouter from './src/routes/organizations.routes'; import recruitmentRouter from './src/routes/recruitment.routes'; +import slackRouter from './src/routes/slack.routes'; const app = express(); const port = process.env.PORT || 3001; @@ -67,6 +68,7 @@ app.use('/templates', workPackageTemplatesRouter); app.use('/cars', carsRouter); app.use('/organizations', organizationRouter); app.use('/recruitment', recruitmentRouter); +app.use('/slack', slackRouter); app.use('/', (_req, res) => { res.json('Welcome to FinishLine'); }); diff --git a/src/backend/src/controllers/slack.controllers.ts b/src/backend/src/controllers/slack.controllers.ts new file mode 100644 index 0000000000..e3c042291b --- /dev/null +++ b/src/backend/src/controllers/slack.controllers.ts @@ -0,0 +1,42 @@ +import { Request, Response } from 'express'; +import crypto from 'crypto'; +import slackServices from '../services/slack.services'; + +export default class slackController { + static async handleEvent(req: Request, res: Response) { + console.log('got a slack req'); + if (req.body.type === 'url_verification') { + return res.status(200).send({ challenge: req.body.challenge }); + } + + const slackSignature = req.headers['x-slack-signature'] as string; + const slackTimeStamp = req.headers['X-Slack-Request-Timestamp'] as string; + + if (Math.abs(Date.now() - Number(slackTimeStamp) * 1000) > 60 * 5 * 1000) { + return res.status(400).send('Slack request verification failed due to expired timestamp'); + } + + const reqBody = req.body; + + const signatureBase = 'v0:' + slackTimeStamp + ':' + reqBody; + + const finalSignature = + 'v0=' + + crypto + .createHmac('sha256', process.env.SLACK_BOT_TOKEN ? process.env.SLACK_BOT_TOKEN : '') + .update(signatureBase) + .digest('hex'); + + if ( + crypto.timingSafeEqual( + Uint8Array.from(Buffer.from(finalSignature, 'utf8')), + Uint8Array.from(Buffer.from(slackSignature, 'utf8')) + ) + ) { + slackServices.processEvent(req.body); + return res.status(200).send('Event recieved'); + } + + return res.status(400).send('Slack request verification failed due to incorrect signature'); + } +} diff --git a/src/backend/src/routes/slack.routes.ts b/src/backend/src/routes/slack.routes.ts new file mode 100644 index 0000000000..bea0984777 --- /dev/null +++ b/src/backend/src/routes/slack.routes.ts @@ -0,0 +1,8 @@ +import express from 'express'; +import slackController from '../controllers/slack.controllers'; + +const slackRouter = express.Router(); + +slackRouter.post('/', slackController.handleEvent); + +export default slackRouter; diff --git a/src/backend/src/services/slack.services.ts b/src/backend/src/services/slack.services.ts new file mode 100644 index 0000000000..641c746314 --- /dev/null +++ b/src/backend/src/services/slack.services.ts @@ -0,0 +1,6 @@ +export default class slackServices { + static async processEvent(req: any ) { + //TODO: process request + console.log(req); + } +} diff --git a/src/backend/src/utils/auth.utils.ts b/src/backend/src/utils/auth.utils.ts index e8f647e57e..5f154073ff 100644 --- a/src/backend/src/utils/auth.utils.ts +++ b/src/backend/src/utils/auth.utils.ts @@ -31,7 +31,8 @@ export const requireJwtProd = (req: Request, res: Response, next: NextFunction) if ( req.path === '/users/auth/login' || // logins dont have cookies yet req.path === '/' || // base route is available so aws can listen and check the health - req.method === 'OPTIONS' // this is a pre-flight request and those don't send cookies + req.method === 'OPTIONS' || // this is a pre-flight request and those don't send cookies + req.path === '/slack' // slack http endpoint is only used from slack api ) { return next(); } else if ( @@ -62,7 +63,8 @@ export const requireJwtDev = (req: Request, res: Response, next: NextFunction) = req.path === '/users/auth/login/dev' || // logins dont have cookies yet req.path === '/' || // base route is available so aws can listen and check the health req.method === 'OPTIONS' || // this is a pre-flight request and those don't send cookies - req.path === '/users' // dev login needs the list of users to log in + req.path === '/users' || // dev login needs the list of users to log in + req.path === '/slack' // slack http endpoint is only used from slack api ) { next(); } else if ( @@ -171,7 +173,8 @@ export const getUserAndOrganization = async (req: Request, res: Response, next: req.path === '/users/auth/login/dev' || req.path === '/' || // base route is available so aws can listen and check the health req.method === 'OPTIONS' || // this is a pre-flight request and those don't send cookies - req.path === '/users' // dev login needs the list of users to log in + req.path === '/users' || // dev login needs the list of users to log in + req.path === '/slack' // slack http endpoint is only used from slack api ) { return next(); }