mirror of
https://github.com/codeflash-ai/codeflash-internal.git
synced 2026-05-04 18:25:18 +00:00
## Summary - **Fix CI build failure**: Auth0Client crashes during Next.js prerendering when env vars aren't set. Returns a no-op stub (`getSession → null`) when domain is missing — semantically correct for static generation - **Lazy-load markdown libs (~260kb)**: ReactMarkdown, remarkGfm, and react-syntax-highlighter were eagerly imported in monaco-diff-viewer but only rendered when user expands "Generated Tests". Extracted into a dynamic component - **Parallelize repo detail query**: `getRepositoryById` ran the activity count sequentially after the repo lookup. Since `repoId` is already available, all three queries now run in parallel ## Test plan - [ ] CI `build` check passes (was failing since #2598) - [ ] Trace page still renders generated tests correctly when expanded - [ ] Repository detail page loads correctly with activity status
151 lines
4.6 KiB
TypeScript
151 lines
4.6 KiB
TypeScript
import { Request, Response } from "express"
|
|
import * as crypto from "node:crypto"
|
|
import { posthog } from "../analytics.js"
|
|
import { processReaction } from "../github/optimization_approval.js"
|
|
import * as Sentry from "@sentry/node"
|
|
import { logger } from "../utils/logger.js"
|
|
import { forbidden, internalServerError } from "../exceptions/index.js"
|
|
|
|
// Dependencies interface for easier testing
|
|
export interface HandleSlackEventsDependencies {
|
|
crypto: typeof crypto
|
|
posthog: typeof posthog
|
|
processReaction: typeof processReaction
|
|
Sentry: typeof Sentry
|
|
getSlackSigningSecret: () => string | undefined
|
|
getCurrentTime: () => number
|
|
console: typeof console
|
|
}
|
|
|
|
// Default dependencies
|
|
let dependencies: HandleSlackEventsDependencies = {
|
|
crypto,
|
|
posthog,
|
|
processReaction,
|
|
Sentry,
|
|
getSlackSigningSecret: () => process.env.SLACK_SIGNING_SECRET,
|
|
getCurrentTime: () => Math.floor(Date.now() / 1000),
|
|
console,
|
|
}
|
|
|
|
// For testing - allow dependency injection
|
|
export function setHandleSlackEventsDependencies(deps: Partial<HandleSlackEventsDependencies>) {
|
|
dependencies = { ...dependencies, ...deps }
|
|
}
|
|
|
|
export function resetHandleSlackEventsDependencies() {
|
|
dependencies = {
|
|
crypto,
|
|
posthog,
|
|
processReaction,
|
|
Sentry,
|
|
getSlackSigningSecret: () => process.env.SLACK_SIGNING_SECRET,
|
|
getCurrentTime: () => Math.floor(Date.now() / 1000),
|
|
console,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify that the request is coming from Slack
|
|
*/
|
|
export function verifySlackRequest(req: Request): boolean {
|
|
const SLACK_SIGNING_SECRET = dependencies.getSlackSigningSecret()
|
|
|
|
if (!SLACK_SIGNING_SECRET) {
|
|
logger.warn("SLACK_SIGNING_SECRET not configured. Skipping request verification.", req)
|
|
return true
|
|
}
|
|
|
|
const slackSignature = req.headers["x-slack-signature"] as string
|
|
const slackTimestamp = req.headers["x-slack-request-timestamp"] as string
|
|
|
|
if (!slackSignature || !slackTimestamp) {
|
|
return false
|
|
}
|
|
|
|
// Prevent replay attacks - reject requests older than 5 minutes
|
|
const currentTime = dependencies.getCurrentTime()
|
|
if (Math.abs(currentTime - parseInt(slackTimestamp)) > 300) {
|
|
return false
|
|
}
|
|
|
|
const requestBody = typeof req.body === "string" ? req.body : JSON.stringify(req.body)
|
|
|
|
const baseString = `v0:${slackTimestamp}:${requestBody}`
|
|
|
|
const hmac = dependencies.crypto.createHmac("sha256", SLACK_SIGNING_SECRET)
|
|
const signature = `v0=${hmac.update(baseString).digest("hex")}`
|
|
|
|
return dependencies.crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(slackSignature))
|
|
}
|
|
|
|
/**
|
|
* Handle Slack events for the approval system
|
|
*/
|
|
export async function handleSlackEvents(req: Request, res: Response) {
|
|
try {
|
|
// Verify the request is from Slack
|
|
if (!verifySlackRequest(req)) {
|
|
logger.error("Failed to verify Slack request", req)
|
|
throw forbidden("Invalid Slack request signature")
|
|
}
|
|
|
|
// Handle URL verification (required when setting up events)
|
|
if (req.body.type === "url_verification") {
|
|
logger.info("Handling Slack URL verification challenge", req)
|
|
return res.json({ challenge: req.body.challenge })
|
|
}
|
|
|
|
// Acknowledge receipt quickly
|
|
res.status(200).send("OK")
|
|
|
|
const event = req.body.event
|
|
if (event) {
|
|
logger.info(`Processing Slack event: ${event.type}`, req, {
|
|
eventType: event.type,
|
|
})
|
|
|
|
if (event.type === "reaction_added") {
|
|
const processed = await dependencies.processReaction(event)
|
|
|
|
if (processed) {
|
|
dependencies.posthog?.capture({
|
|
distinctId: "system",
|
|
event: "slack-approval-reaction-processed",
|
|
properties: {
|
|
reaction: event.reaction,
|
|
result: "success",
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// Re-throw AppExceptions to be handled by GlobalExceptionHandler if headers not sent
|
|
if (error && typeof error === "object" && "getHttpStatus" in error) {
|
|
if (!res.headersSent) {
|
|
throw error
|
|
}
|
|
// If headers already sent, just log it
|
|
logger.errorWithSentry(`Error handling Slack event: ${error}`, req, {}, error as Error)
|
|
dependencies.Sentry.captureException(error)
|
|
return
|
|
}
|
|
|
|
logger.errorWithSentry(`Error handling Slack event: ${error}`, req, {}, error as Error)
|
|
|
|
// If we haven't sent a response yet, throw an error
|
|
if (!res.headersSent) {
|
|
throw internalServerError("Error processing Slack event")
|
|
}
|
|
dependencies.Sentry.captureException(error)
|
|
// Log to monitoring
|
|
dependencies.posthog?.capture({
|
|
distinctId: "system",
|
|
event: "slack-optimization-approval-error",
|
|
properties: {
|
|
error: String(error),
|
|
},
|
|
})
|
|
}
|
|
}
|