({ request, rawBody, requestId, providerConfig }: AuthContext)
| 450 | |
| 451 | export const slackHandler: WebhookProviderHandler = { |
| 452 | verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) { |
| 453 | const signingSecret = providerConfig.signingSecret as string | undefined |
| 454 | if (!signingSecret) { |
| 455 | return null |
| 456 | } |
| 457 | |
| 458 | const signature = request.headers.get('x-slack-signature') |
| 459 | const timestamp = request.headers.get('x-slack-request-timestamp') |
| 460 | |
| 461 | if (!signature || !timestamp) { |
| 462 | logger.warn(`[${requestId}] Slack webhook missing signature or timestamp header`) |
| 463 | return new NextResponse('Unauthorized - Missing Slack signature', { status: 401 }) |
| 464 | } |
| 465 | |
| 466 | const now = Math.floor(Date.now() / 1000) |
| 467 | const parsedTimestamp = Number(timestamp) |
| 468 | if (Number.isNaN(parsedTimestamp)) { |
| 469 | logger.warn(`[${requestId}] Slack webhook timestamp is not a valid number`, { timestamp }) |
| 470 | return new NextResponse('Unauthorized - Invalid timestamp', { status: 401 }) |
| 471 | } |
| 472 | const skew = Math.abs(now - parsedTimestamp) |
| 473 | if (skew > SLACK_TIMESTAMP_MAX_SKEW) { |
| 474 | logger.warn(`[${requestId}] Slack webhook timestamp too old`, { |
| 475 | timestamp, |
| 476 | now, |
| 477 | skew, |
| 478 | }) |
| 479 | return new NextResponse('Unauthorized - Request timestamp too old', { status: 401 }) |
| 480 | } |
| 481 | |
| 482 | if (!validateSlackSignature(signingSecret, signature, timestamp, rawBody)) { |
| 483 | logger.warn(`[${requestId}] Slack signature verification failed`) |
| 484 | return new NextResponse('Unauthorized - Invalid Slack signature', { status: 401 }) |
| 485 | } |
| 486 | |
| 487 | return null |
| 488 | }, |
| 489 | |
| 490 | handleChallenge(body: unknown) { |
| 491 | return handleSlackChallenge(body) |
nothing calls this directly
no test coverage detected