(req: Request, res: Response, next: NextFunction)
| 61 | * before this middleware to preserve the raw body. |
| 62 | */ |
| 63 | export function verifySlackRequest(req: Request, res: Response, next: NextFunction): void { |
| 64 | if (!SLACK_SIGNING_SECRET) { |
| 65 | logger.warn('SLACK_SIGNING_SECRET not configured, skipping verification'); |
| 66 | next(); |
| 67 | return; |
| 68 | } |
| 69 | |
| 70 | const signature = req.headers['x-slack-signature'] as string; |
| 71 | const timestamp = req.headers['x-slack-request-timestamp'] as string; |
| 72 | |
| 73 | if (!signature || !timestamp) { |
| 74 | logger.warn('Missing Slack signature headers'); |
| 75 | res.status(401).json({ error: 'Missing signature headers' }); |
| 76 | return; |
| 77 | } |
| 78 | |
| 79 | // Get raw body - express.json() parses it, so we need to reconstruct |
| 80 | // For URL-encoded forms (slash commands), we need the raw body |
| 81 | const rawBody = typeof req.body === 'string' |
| 82 | ? req.body |
| 83 | : JSON.stringify(req.body); |
| 84 | |
| 85 | const isValid = verifySlackSignature( |
| 86 | SLACK_SIGNING_SECRET, |
| 87 | signature, |
| 88 | timestamp, |
| 89 | rawBody |
| 90 | ); |
| 91 | |
| 92 | if (!isValid) { |
| 93 | logger.warn('Invalid Slack signature'); |
| 94 | res.status(401).json({ error: 'Invalid signature' }); |
| 95 | return; |
| 96 | } |
| 97 | |
| 98 | next(); |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * Check if Slack signing secret is configured |
nothing calls this directly
no test coverage detected