import { ServerResponse , IncomingHttpHeaders , IncomingMessage } from 'http' ; import * as querystring from 'querystring' ; import debugFactory from 'debug' ; import getRawBody from 'raw-body' ; import crypto from 'crypto' ; import timingSafeCompare from 'tsscmp' ; import SlackMessageAdapter from './adapter' ; import { ErrorCode , errorWithCode } from './errors' ; import { packageIdentifier , isFalsy } from './util' ; const debug = debugFactory ( '@slack/interactive-messages:http-handler' ) ; export function createHTTPHandler ( adapter ) { const poweredBy = packageIdentifier ( ) ; param res returns function sendResponse ( res ) { return ( { status , content } ) => { res . statusCode = status ; res . setHeader ( 'X-Slack-Powered-By' , poweredBy ) ; if ( typeof content === 'string' ) { res . end ( content ) ; } else if ( ! isFalsy ( content ) ) { res . setHeader ( 'Content-Type' , 'application/json' ) ; res . end ( JSON . stringify ( content ) ) ; } else { res . end ( ) ; } } ; } param body returns function parseBody ( body ) { const parsedBody = querystring . parse ( body ) ; if ( ! isFalsy ( parsedBody . payload ) ) { return JSON . parse ( parsedBody . payload as string ) ; } return parsedBody ; } param signingSecret param requestHeaders param body returns function verifyRequestSignature ( signingSecret , requestHeaders , body ) { const signature = requestHeaders [ 'x-slack-signature' ] ; const ts = parseInt ( requestHeaders [ 'x-slack-request-timestamp' ] , 10 ) ; const fiveMinutesAgo = Math . floor ( Date . now ( ) / 1000 ) - ( 60 * 5 ) ; if ( ts < fiveMinutesAgo ) { debug ( 'request is older than 5 minutes' ) ; throw errorWithCode ( new Error ( 'Slack request signing verification outdated' ) , ErrorCode . RequestTimeFailure ) ; } const hmac = crypto . createHmac ( 'sha256' , signingSecret ) ; const [ version , hash ] = signature . split ( '=' ) ; hmac . update ( `${ version }:${ ts }:${ body }` ) ; if ( ! timingSafeCompare ( hash , hmac . digest ( 'hex' ) ) ) { debug ( 'request signature is not valid' ) ; throw errorWithCode ( new Error ( 'Slack request signing verification failed' ) , ErrorCode . SignatureVerificationFailure ) ; } debug ( 'request signing verification success' ) ; return true ; } /**
     * Request listener used to handle Slack requests and send responses and
     * verify request signatures
     */ return ( req , res ) => { debug ( 'request received - method: %s, path: %s' , req . method , req . url ) ; const respond = sendResponse ( res ) ; if ( ! isFalsy ( req . body ) && isFalsy ( req . rawBody ) ) { respond ( { status : 500 , content : process . env . NODE_ENV === 'development' ? 'Parsing request body prohibits request signature verification' : undefined , } ) ; return ; } let parseRawBody ; if ( ! isFalsy ( req . rawBody ) ) { debug ( 'Parsing request with a rawBody attribute' ) ; parseRawBody = Promise . resolve ( req . rawBody ) ; } else { debug ( 'Parsing raw request' ) ; parseRawBody = getRawBody ( req ) ; } parseRawBody . then ( ( bodyBuf ) => { const rawBody = bodyBuf . toString ( ) ; if ( verifyRequestSignature ( adapter . signingSecret , req . headers as VerificationHeaders , rawBody ) ) { const body = parseBody ( rawBody ) ; if ( body . ssl_check ) { respond ( { status : 200 } ) ; return ; } const dispatchResult = adapter . dispatch ( body ) ; if ( dispatchResult !== undefined ) { dispatchResult . then ( respond ) ; } else { debug ( 'no callback was matched' ) ; respond ( { status : 404 } ) ; } } } ) . catch ( ( error ) => { if ( error . code === ErrorCode . SignatureVerificationFailure || error . code === ErrorCode . RequestTimeFailure ) { respond ( { status : 404 } ) ; } else if ( process . env . NODE_ENV === 'development' ) { respond ( { status : 500 , content : error . message } ) ; } else { respond ( { status : 500 } ) ; } } ) ; } ; } remarks type HTTPHandler = ( req : IncomingMessage & { body ? : any ; rawBody ? : Buffer ; } , res : ServerResponse ) => void ; /**
 * A response handler returned by `sendResponse`.
 */ type ResponseHandler = ( dispatchResult : { status : number ; content ? : string | Record < string , unknown > ; } ) => void ; /**
 * Headers required for verification.
 *
 * See: https://api.slack.com/docs/verifying-requests-from-slack
 */ export interface VerificationHeaders extends IncomingHttpHeaders { 'x-slack-signature' : string ; 'x-slack-request-timestamp' : string ; }