packages feed

hoauth2-providers-0.9.0: src/Network/OIDC/WellKnown.hs

{-# LANGUAGE CPP #-}

module Network.OIDC.WellKnown where

import Control.Monad.Except
#if MIN_VERSION_base(4,18,0)
import Control.Monad.IO.Class
#endif
import Data.Aeson
import Data.Aeson.Types
import Data.Bifunctor
import Data.ByteString.Lazy (ByteString)
import Data.Text.Lazy (Text)
import Data.Text.Lazy qualified as TL
import Data.Text.Lazy.Encoding qualified as TL
import Network.HTTP.Simple
import Network.HTTP.Types.Status
import URI.ByteString
import URI.ByteString.Aeson ()

-- | Slim OpenID Configuration
--
-- See spec <https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata>
data OpenIDConfiguration = OpenIDConfiguration
  { issuer :: URI
  , authorizationEndpoint :: URI
  , tokenEndpoint :: URI
  , userinfoEndpoint :: URI
  , jwksUri :: URI
  , deviceAuthorizationEndpoint :: URI
  }
  deriving (Show, Eq)

instance FromJSON OpenIDConfiguration where
  parseJSON :: Value -> Parser OpenIDConfiguration
  parseJSON = withObject "parseJSON OpenIDConfiguration" $ \t -> do
    issuer <- t .: "issuer"
    authorizationEndpoint <- t .: "authorization_endpoint"
    tokenEndpoint <- t .: "token_endpoint"
    userinfoEndpoint <- t .: "userinfo_endpoint"
    jwksUri <- t .: "jwks_uri"
    deviceAuthorizationEndpoint <- t .: "device_authorization_endpoint"
    pure OpenIDConfiguration {..}

wellknownUrl :: TL.Text
wellknownUrl = "/.well-known/openid-configuration"

fetchWellKnown ::
  MonadIO m =>
  -- | Domain
  TL.Text ->
  ExceptT Text m OpenIDConfiguration
fetchWellKnown domain = ExceptT $ do
  let uri = "https://" <> domain <> wellknownUrl
  req <- liftIO $ parseRequest (TL.unpack uri)
  resp <- httpLbs req
  pure (handleWellKnownResponse resp)

handleWellKnownResponse :: Response ByteString -> Either Text OpenIDConfiguration
handleWellKnownResponse resp = do
  let rawBody = getResponseBody resp
  let rStatus = getResponseStatus resp
  if rStatus == status200
    then first (("handleWellKnownResponse decode response failed: " <>) . TL.pack) (eitherDecode rawBody)
    else Left $ "handleWellKnownResponse failed: " <> TL.pack (show rStatus) <> TL.decodeUtf8 rawBody