packages feed

wai-middleware-slack-verify-0.1.0.0: test/WaiMiddlewareSlackVerifySpec.hs

module WaiMiddlewareSlackVerifySpec
    ( spec
    ) where

import           Crypto.Hash
import           Crypto.Hash.Algorithms
import           Crypto.MAC.HMAC
import           Data.ByteArray.Encoding (convertToBase, Base(Base16))
import           Data.ByteString                 (ByteString)
import qualified Data.ByteString                 as BS
import qualified Data.ByteString.Char8           as BC
import           Data.Time.Clock.POSIX
import           Test.Hspec
import           Test.HUnit                      hiding (Test)
import           Network.Wai
import           Network.Wai.Test
import           Network.Wai.Middleware.SlackVerify
import           Network.HTTP.Types

spec :: Spec
spec = describe "Network.Wai.Middleware.SlackVerify" $ do
    it "authenticates valid signatures" caseValidSignature
    it "rejects missing signature"      caseMissingSignature
    it "rejects signature mismatch"     caseSignatureMismatch

testApp :: ByteString -> FailureResponse -> Application
testApp secret h =
    verifySlackRequest' secret h $ \_ f -> f $ responseLBS status200 [] ""

testSecret = "<RANDOM SECRET>"

testHandler e _ res = res $ responseLBS status403 [] msg
  where
    msg = case e of
        NoSignature       -> "No signature"
        SignatureMismatch -> "Signature Mismatch"

createHeaders :: ByteString -> ByteString -> [(HeaderName, ByteString)]
createHeaders sig timestamp =
    [ ("Content-Type"             , "application/json")
    , ("X-Slack-Request-Timestamp", timestamp)
    , ("X-Slack-Signature"        , sig)
    ]

getTimestamp :: IO ByteString
getTimestamp = BC.pack . show . floor <$> getPOSIXTime

caseValidSignature :: Assertion
caseValidSignature = do
    timestamp <- getTimestamp
    flip runSession (testApp testSecret testHandler) $ do
        res <- request defaultRequest
            { requestHeaders = createHeaders (generateSignature timestamp)
                                             timestamp
            }
        assertStatus 200 res
        assertBody   ""  res
  where
    generateSignature t =
        let
            HMAC h =
                hmac testSecret $ (BS.concat ["v0", ":", t, ":", ""]) :: HMAC SHA256
        in  BS.append "v0=" (convertToBase Base16 h)

caseMissingSignature :: Assertion
caseMissingSignature = do
    flip runSession (testApp testSecret testHandler) $ do
        res <- request defaultRequest
        assertStatus 403            res
        assertBody   "No signature" res

caseSignatureMismatch :: Assertion
caseSignatureMismatch = do
    timestamp <- getTimestamp
    flip runSession (testApp testSecret testHandler) $ do
        res <- request defaultRequest
            { requestHeaders = createHeaders "<invalid>" timestamp
            }
        assertStatus 403                  res
        assertBody   "Signature Mismatch" res