{-# LANGUAGE OverloadedStrings #-}

{-|
Module      : Web.Slack.Auth
Description : Slack Verification
Copyright   : (c) Mo Kweon
Maintainer  : kkweon@gmail.com

This module provides a single function @verify@ which can be used to verify your Slack bot
-}
module Web.Slack.Auth where

import Crypto.Hash (Digest, SHA256, digestFromByteString)
import Crypto.MAC.HMAC (HMAC(HMAC), hmac)
import Data.ByteString (ByteString)
import Data.ByteString.Base16 (decode)
import Data.String (fromString)

import qualified Data.ByteString as B

-- | SlackSigningToken is your Slack Signing Secret
newtype SlackSigningToken =
  SlackSigningToken ByteString
  deriving (Eq, Show)

-- | Timestamp is sent from __X-Slack-Request-Timestamp__ in the request header
newtype Timestamp =
  Timestamp Int
  deriving (Eq, Show, Ord)

-- | Hex is retrieved from __X-Slack-Signature__ in the request header
--
-- Note you don't have to strip "v0=" so you can just pass the header value directly
newtype Hex =
  Hex ByteString
  deriving (Eq, Show)

-- VerificationError occurs when wrong hex is given
newtype VerificationError =
  WrongHex String
  deriving (Eq)

instance Show VerificationError where
  show (WrongHex err) = "[Wrong Hex Code] " ++ err

-- | Verify verifies Slack Request
--
-- Example
--
-- >>> slackSecret = SlackSigningToken "8f742231b10e8888abcd99yyyzzz85a5"
-- >>> timestamp = Timestamp 1531420618
-- >>> body = "token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c"
-- >>> expectedHash = Hex "v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503"
-- >>> verify slackSecret timestamp body expectedHash
-- Right True
verify ::
     SlackSigningToken -- ^ Slack Token
  -> Timestamp -- ^ X-SlackRequest-Timestamp Header Value
  -> ByteString -- ^ Request body sent by Slack (urlencoded)
  -> Hex -- ^ X-Slack-Signature Header Value (The HMAC will be compared to this value)
  -> Either VerificationError Bool
verify (SlackSigningToken _token) (Timestamp ts) body (Hex _hex) = do
  let sigBaseString =
        fromString "v0:" <> fromString (show ts) <> fromString ":" <> body
  normalizedHex <-
    case B.stripPrefix (fromString "v0=") _hex of
      Just xs -> Right xs
      _ -> Left $ WrongHex ("Unable to strip prefix v0= from " ++ show _hex)
  hexDecoded <-
    case decode normalizedHex of
      (decoded, "") -> Right decoded
      (_, x) -> Left $ WrongHex ("Failed to decode Hex" ++ show x)
  expectedHmac <-
    case digestFromByteString hexDecoded :: Maybe (Digest SHA256) of
      Just digest -> Right (HMAC digest)
      _ -> Left $ WrongHex "Failed to digest from bytestrng"
  return $ hmac _token sigBaseString == expectedHmac