acme-not-a-joke-0.1.0.0: src/Acme/NotAJoke/Api/Nonce.hs
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{- | Almost all ACME API Calls require a Nonce to prevent replayability of
requests.
Most API Calls return a Nonce for the next request.
Client should re-use these Nonce to avoid overloading the server.
This module provide helpers to deal with this requirement.
-}
module Acme.NotAJoke.Api.Nonce where
import Control.Lens hiding ((.=))
import Data.Aeson (FromJSON (..), ToJSON (..))
import Data.ByteString.Lazy (ByteString)
import Data.Coerce (Coercible, coerce)
import Data.IORef (atomicModifyIORef, newIORef, writeIORef)
import Data.Text (Text)
import qualified Data.Text.Encoding as Encoding
import qualified Network.Wreq as Wreq
import Acme.NotAJoke.Api.Endpoint
newtype Nonce = Nonce Text
deriving (Show, FromJSON, ToJSON)
getNonce :: Endpoint "newNonce" -> IO (Maybe Nonce)
getNonce ep = do
r <- Wreq.head_ (wrequrl ep)
pure $ responseNonceWreq r
where
responseNonceWreq :: forall a. Wreq.Response a -> Maybe Nonce
responseNonceWreq r =
r ^? Wreq.responseHeader "replay-nonce" . to Encoding.decodeUtf8 . to Nonce
responseNonceWreqBS :: Wreq.Response ByteString -> Maybe Nonce
responseNonceWreqBS = responseNonceWreq
responseNonce :: forall a. (Coercible a (Wreq.Response ByteString)) => a -> Maybe Nonce
responseNonce = responseNonceWreqBS . coerce
data Fetcher = Fetcher
{ produce :: IO (Maybe Nonce)
, set :: Nonce -> IO ()
, fetchNewNonce :: IO (Maybe Nonce)
}
fetcher :: IO (Maybe Nonce) -> IO Fetcher
fetcher fetch = do
ref <- newIORef Nothing
pure $ Fetcher (go ref) (writeIORef ref . Just) fetch
where
go ref = do
val <- atomicModifyIORef ref (\x -> (Nothing, x))
case val of
Nothing -> fetch
(Just x) -> pure (Just x)
saveResponseNonce :: forall a. (Coercible a (Wreq.Response ByteString)) => Fetcher -> a -> IO ()
saveResponseNonce nonceFetcher rsp =
maybe (pure ()) (nonceFetcher.set) (responseNonce rsp)
saveNonce :: forall a. (Coercible a (Wreq.Response ByteString)) => Fetcher -> IO (Maybe a) -> IO (Maybe a)
saveNonce nonceFetcher apiCall = do
obj <- apiCall
maybe (pure ()) (saveResponseNonce nonceFetcher) obj
pure obj