{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE TemplateHaskell   #-}
module Network.Wai.Auth.Config
  ( AuthConfig(..)
  , SecretKey(..)
  , Service(..)
  , FileServer(..)
  , ReverseProxy(..)
  , encodeKey
  , decodeKey
  ) where

import           Data.Aeson
import           Data.Aeson.TH          (defaultOptions, deriveJSON,
                                         fieldLabelModifier)
import qualified Data.Text              as T
import           Data.Text.Encoding     (encodeUtf8)
import           Network.Wai.Auth.Tools (decodeKey, encodeKey,
                                         toLowerUnderscore)
import           Web.ClientSession      (Key)

-- | Configuration for a secret key that will be used to encrypt authenticated
-- user as client side cookie.
data SecretKey
  = SecretKeyFile FilePath -- ^ Path to a secret key file in binary form, if it
                           -- is malformed or doesn't exist it will be
                           -- (re)created. If empty "client_session_key.aes"
                           -- name will be used
  | SecretKey Key -- ^ Serialized and base64 encoded form of a secret key. Use
                  -- `encodeKey` to get a proper encoded form.


-- | Configuration for reverse proxy application.
data FileServer = FileServer
    { fsRootFolder       :: FilePath -- ^ Path to a folder containing files
                                     -- that will be served by this app.
    , fsRedirectToIndex  :: Bool -- ^ Redirect to the actual index file, not
                                 -- leaving the URL containing the directory
                                 -- name
    , fsAddTrailingSlash :: Bool -- ^ Add a trailing slash to directory names
    }

-- | Configuration for reverse proxy application.
data ReverseProxy = ReverseProxy
    { rpHost :: T.Text -- ^ Hostname of the webserver
    , rpPort :: Int -- ^ Port of the webserver
    }

-- | Available services.
data Service = ServiceFiles FileServer
             | ServiceProxy ReverseProxy

-- | Configuration for @wai-auth@ executable and any other, that is created using
-- `Network.Wai.Auth.Executable.mkMain`
data AuthConfig = AuthConfig
  { configAppRoot    :: Maybe T.Text -- ^ Root Url of the website, eg:
                                     -- http://example.com or
                                     -- https://example.com It will be used to
                                     -- perform redirects back from external
                                     -- authentication providers.
  , configAppPort    :: Int  -- ^ Port number. Default is 3000
  , configRequireTls :: Bool -- ^ Require requests come in over a secure
                             -- connection (determined via headers). Will
                             -- redirect to HTTPS if non-secure
                             -- dedected. Default is @False@
  , configSkipAuth   :: Bool -- ^ Turn off authentication middleware, useful for
                             -- testing. Default is @False@
  , configCookieAge  :: Int -- ^ Duration of the session in seconds. Default is
                            -- one hour (3600 seconds).
  , configSecretKey  :: SecretKey -- ^ Secret key. Default is "client_session_key.aes"
  , configService    :: Service
  , configProviders  :: Object
  }


instance FromJSON AuthConfig where
  parseJSON =
    withObject "Auth Config Object" $ \obj -> do
      configAppRoot <- obj .:? "app_root"
      configAppPort <- obj .:? "app_port" .!= 3000
      configRequireTls <- (obj .:? "require_tls" .!= False)
      configSkipAuth <- obj .:? "skip_auth" .!= False
      configCookieAge <- obj .:? "cookie_age" .!= 3600
      mSecretKeyB64T <- obj .:? "secret_key"
      configSecretKey <-
        case mSecretKeyB64T of
          Just secretKeyB64T ->
            either fail (return . SecretKey) $ decodeKey (encodeUtf8 secretKeyB64T)
          Nothing -> SecretKeyFile <$> (obj .:? "secret_key_file" .!= "")
      mFileServer <- obj .:? "file_server"
      mReverseProxy <- obj .:? "reverse_proxy"
      let sErrMsg =
            "Either 'file_server' or 'reverse_proxy' is required, but not both."
      configService <-
        case (mFileServer, mReverseProxy) of
          (Just fileServer, Nothing) -> ServiceFiles <$> parseJSON fileServer
          (Nothing, Just reverseProxy) ->
            ServiceProxy <$> parseJSON reverseProxy
          (Just _, Just _) -> fail $ "Too many services. " ++ sErrMsg
          (Nothing, Nothing) -> fail $ "No service is supplied. " ++ sErrMsg
      configProviders <- obj .: "providers"
      return AuthConfig {..}

$(deriveJSON defaultOptions { fieldLabelModifier = toLowerUnderscore . drop 2} ''FileServer)

$(deriveJSON defaultOptions { fieldLabelModifier = toLowerUnderscore . drop 2} ''ReverseProxy)