packages feed

acme-not-a-joke-0.1.0.0: src/Acme/NotAJoke/Api/Authorization.hs

module Acme.NotAJoke.Api.Authorization where

import Control.Lens hiding ((.=))
import Data.Aeson (FromJSON (..), ToJSON (..), decode, encode, object, pairs, withObject, withText, (.:), (.:?), (.=))
import Data.ByteString.Lazy (ByteString)
import Data.Coerce (coerce)
import Data.Text (Text)
import Data.Time.Clock (UTCTime)
import qualified Network.Wreq as Wreq

import qualified Crypto.JOSE.JWS as JWS

import Acme.NotAJoke.Api.Challenge
import Acme.NotAJoke.Api.Endpoint
import Acme.NotAJoke.Api.Field
import Acme.NotAJoke.Api.JWS
import Acme.NotAJoke.Api.Nonce

-- RFC-defined authorization statuses.
data AuthorizationStatus
    = AuthorizationPending
    | AuthorizationValid
    | AuthorizationInvalid
    | AuthorizationDeactivated
    | AuthorizationExpired
    | AuthorizationRevoked
    deriving (Show, Eq, Ord)

instance FromJSON AuthorizationStatus where
    parseJSON = withText "AuthorizationStatus" $ \txt ->
        case txt of
            "pending" -> pure AuthorizationPending
            "valid" -> pure AuthorizationValid
            "invalid" -> pure AuthorizationInvalid
            "deactivated" -> pure AuthorizationDeactivated
            "expired" -> pure AuthorizationExpired
            "revoked" -> pure AuthorizationRevoked
            _ -> fail $ "invalid authorization status:" <> show txt

-- RFC-defined authorization types (only the subset supported in this library).
data AuthorizationType
    = DNSAuthorization
    deriving (Show, Eq, Ord)

instance ToJSON AuthorizationType where
    toEncoding DNSAuthorization = toEncoding ("dns" :: Text)
    toJSON DNSAuthorization = toJSON ("dns" :: Text)
instance FromJSON AuthorizationType where
    parseJSON = withText "Authorizationtype" $ \txt ->
        case txt of
            "dns" -> pure DNSAuthorization
            _ -> fail $ "invalid ordertype:" <> show txt

-- | RFC-defined authorization identifiers.
data AuthorizationIdentifier
    = AuthorizationIdentifier
    { type_ :: AuthorizationType
    , value :: Text
    }
    deriving (Show, Eq, Ord)

instance ToJSON AuthorizationIdentifier where
    toEncoding o = pairs ("type" .= o.type_ <> "value" .= o.value)
    toJSON o = object ["type" .= o.type_, "value" .= o.value]
instance FromJSON AuthorizationIdentifier where
    parseJSON = withObject "AuthorizationIdentifier" $ \o ->
        AuthorizationIdentifier
            <$> o .: "type"
            <*> o .: "value"

-- | RFC-defined authorization resource.
data Authorization a
    = Authorization
    { status :: Field a "status" AuthorizationStatus
    , identifier :: Field a "status" AuthorizationIdentifier
    , expires :: Field a "expires" (Maybe UTCTime)
    , challenges :: Field a "challenges" [Challenge "challenge-unspecified"]
    , wildcard :: Field a "wildcard" (Maybe Bool)
    }

newtype AuthorizationInspected = AuthorizationInspected (Wreq.Response ByteString)
    deriving (Show)

type instance Field "authorization-inspected" "status" x = x
type instance Field "authorization-inspected" "identifier" x = x
type instance Field "authorization-inspected" "expires" x = x
type instance Field "authorization-inspected" "challenges" x = x
type instance Field "authorization-inspected" "wildcard" x = x

instance FromJSON (Authorization "authorization-inspected") where
    parseJSON = withObject "Authorization(inspected)" $ \v ->
        Authorization
            <$> v .: "status"
            <*> v .: "identifier"
            <*> v .: "expires"
            <*> v .: "challenges"
            <*> v .:? "wildcard"

-- | Lookup an Authorization.
readAuthorization :: AuthorizationInspected -> Maybe (Authorization "authorization-inspected")
readAuthorization (AuthorizationInspected rsp) = decode $ rsp ^. Wreq.responseBody

-- | Inspects an authorization from its URL.
postGetAuthorization :: JWS.JWK -> KID -> Url "authorization" -> Nonce -> IO (Maybe AuthorizationInspected)
postGetAuthorization jwk kid authUrl nonce = do
    let opts = Wreq.defaults & Wreq.header "Content-Type" .~ ["application/jose+json"]
    ebody <- (kidSign jwk ep kid nonce "")
    case ebody of
        Right body -> do
            e <- Wreq.postWith opts (wrequrl ep) $ encode body
            pure $ Just $ AuthorizationInspected e
        Left err -> do
            print err
            pure Nothing
  where
    ep :: Endpoint "authorization"
    ep = coerce authUrl