packages feed

stripe-scotty (empty) → 1.0.0.0

raw patch · 4 files changed

+270/−0 lines, 4 filesdep +aesondep +basedep +bytestring

Dependencies added: aeson, base, bytestring, http-types, scotty, stripe-concepts, stripe-signature, text, unordered-containers

Files

+ changelog.md view
@@ -0,0 +1,12 @@+# Changelog++All notable changes to this package will be documented in this file.++The format is based on+[Keep a Changelog](https://keepachangelog.com/en/1.0.0/),+and this project adheres to the+[Haskell Package Versioning Policy](https://pvp.haskell.org/).++## 1.0.0.0 - 2018-12-20++- Initial release
+ library/Stripe/Scotty.hs view
@@ -0,0 +1,196 @@+{-# OPTIONS_GHC -Wall #-}++{- | For a typical webhook server implemented with Scotty, probably the only+thing you need from this module is one of these two actions:++  - 'requireSig' - If your live mode and test mode webhooks use separate+    URLs.++  - 'requireSig_eitherMode' - If your live mode and test mode webhooks use+    the same URL. -}++module Stripe.Scotty+  ( requireSig, requireSig_eitherMode+  , hasValidSig, getSig, getSigText+  ) where++-- aeson+import qualified Data.Aeson++-- base+import Control.Monad (when, (>=>))++-- bytestring+import qualified Data.ByteString+import qualified Data.ByteString.Lazy++-- http-types+import qualified Network.HTTP.Types.Status++-- scotty+import qualified Web.Scotty++-- stripe-concepts+import qualified Stripe.Concepts as Stripe++-- stripe-signature+import qualified Stripe.Signature as Stripe++-- text+import qualified Data.Text+import qualified Data.Text.Lazy+import qualified Data.Text.Lazy.Builder++-- unordered-containers+import qualified Data.HashMap.Strict++{- | Terminates request processing if the request does not contain a valid+Stripe signature header.++This action returns the request body as a strict byte string; it has to fully+evaluate the request body to do the signature check, so we might as well return+this information to you for subsequent use. -}++requireSig+    :: Stripe.WebhookSecretKey+    -> Web.Scotty.ActionM Data.ByteString.ByteString++requireSig secret =+  do+    body <- getBody+    okay <- hasValidSig secret body++    when (not okay) invalidSigAction++    return body++{- | Terminates request processing if the request does not contain a valid+Stripe signature header.++This action returns the mode and JSON request body; it has to do this much+parsing to determine the mode so that we can know which secret key to use in the+verification, so we might as well return this information to you for subsequent+use. -}++requireSig_eitherMode+    :: Stripe.BothModes (Maybe Stripe.WebhookSecretKey)+    -> Web.Scotty.ActionM (Stripe.Mode, Data.Aeson.Value)++requireSig_eitherMode secrets =+  do+    body   <- getBody+    value  <- parseBody body+    mode   <- getMode value+    secret <- chooseSecret mode secrets+    okay   <- hasValidSig secret body++    when (not okay) invalidSigAction++    return (mode, value)++getBody :: Web.Scotty.ActionM Data.ByteString.ByteString+getBody = Data.ByteString.Lazy.toStrict <$> Web.Scotty.body++invalidSigAction :: Web.Scotty.ActionM a+invalidSigAction =+  do+    Web.Scotty.status Network.HTTP.Types.Status.forbidden403+    Web.Scotty.text (Data.Text.Lazy.pack "Invalid Stripe signature")+    Web.Scotty.finish++missingKeyAction :: Stripe.Mode -> Web.Scotty.ActionM a+missingKeyAction mode =+  do+    Web.Scotty.status Network.HTTP.Types.Status.internalServerError500+    Web.Scotty.text message+    Web.Scotty.finish+  where+    message =+        Data.Text.Lazy.Builder.toLazyText $+            foldMap Data.Text.Lazy.Builder.fromString+                [ "Configuration error: No webhook secret for "+                , case mode of+                    Stripe.LiveMode -> "live"+                    Stripe.TestMode -> "test"+                , " mode."+                ]++chooseSecret+    :: Stripe.Mode+    -> Stripe.BothModes (Maybe Stripe.WebhookSecretKey)+    -> Web.Scotty.ActionM Stripe.WebhookSecretKey++chooseSecret mode secrets =+    case Stripe.applyMode mode secrets of+        Just x -> return x+        Nothing -> missingKeyAction mode++parseBody+    :: Data.ByteString.ByteString+    -> Web.Scotty.ActionM Data.Aeson.Value++parseBody bs =+    case Data.Aeson.eitherDecode (Data.ByteString.Lazy.fromStrict bs) of+        Left x ->+          do+            Web.Scotty.status Network.HTTP.Types.Status.badRequest400+            Web.Scotty.text (Data.Text.Lazy.pack x)+            Web.Scotty.finish+        Right x ->+            return x++getMode :: Data.Aeson.Value -> Web.Scotty.ActionM Stripe.Mode+getMode val =+    case (aesonAttr "livemode" >=> aesonBool) val of++      Nothing ->+        do+          Web.Scotty.status Network.HTTP.Types.Status.badRequest400+          Web.Scotty.text (Data.Text.Lazy.pack+              "Webhook attribute \"livemode\" is missing.")+          Web.Scotty.finish++      Just livemode ->+          return (Stripe.isLiveMode' livemode)++{- | Determines whether the request contains a valid Stripe signature header. -}++hasValidSig+    :: Stripe.WebhookSecretKey+    -> Data.ByteString.ByteString+    -> Web.Scotty.ActionM Bool++hasValidSig secret body =+  do+    sigMaybe <- getSig+    return $+        case sigMaybe of+            Nothing -> False+            Just sig -> Stripe.isSigValid sig secret body++getSigText :: Web.Scotty.ActionM (Maybe Data.Text.Text)+getSigText =+  do+    x <- Web.Scotty.header (Data.Text.Lazy.pack "Stripe-Signature")+    return (Data.Text.Lazy.toStrict <$> x)++getSig :: Web.Scotty.ActionM (Maybe Stripe.Sig)+getSig =+  do+    x <- getSigText+    return (x >>= Stripe.parseSig)++------------------------------------------------------------++-- Internal Aeson decoding functions++aesonAttr :: String -> Data.Aeson.Value -> Maybe Data.Aeson.Value+aesonAttr x = aesonObject >=> Data.HashMap.Strict.lookup (Data.Text.pack x)++aesonObject :: Data.Aeson.Value -> Maybe Data.Aeson.Object+aesonObject (Data.Aeson.Object x) = Just x+aesonObject _ = Nothing++aesonBool :: Data.Aeson.Value -> Maybe Bool+aesonBool (Data.Aeson.Bool x) = Just x+aesonBool _ = Nothing
+ license.txt view
@@ -0,0 +1,18 @@+Copyright 2018 Typeclass Consulting, LLC++Permission is hereby granted, free of charge, to any person obtaining a copy of+this software and associated documentation files (the "Software"), to deal in+the Software without restriction, including without limitation the rights to+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of+the Software, and to permit persons to whom the Software is furnished to do so,+subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ stripe-scotty.cabal view
@@ -0,0 +1,44 @@+name: stripe-scotty+version: 1.0.0.0++synopsis: Listen for Stripe webhook events with Scotty+category: Web++description:+    This package provides support for writing a+    <https://stripe.com/ Stripe> webhook server using Scotty.++homepage:    https://github.com/typeclasses/stripe+bug-reports: https://github.com/typeclasses/stripe/issues++author:     Chris Martin+maintainer: Chris Martin, Julie Moronuki++copyright: 2018 Typeclass Consulting, LLC+license: MIT+license-file: license.txt++build-type: Simple+cabal-version: >=1.10+tested-with: GHC==8.2.2, GHC==8.4.3, GHC==8.6.1++extra-source-files:+    changelog.md++library+    hs-source-dirs: library+    default-language: Haskell2010++    exposed-modules:+        Stripe.Scotty++    build-depends:+        aeson+      , base >=4.10 && <5+      , bytestring+      , http-types+      , scotty+      , stripe-concepts+      , stripe-signature+      , text+      , unordered-containers