wai-middleware-auth-0.1.0.0: src/Network/Wai/Auth/AppRoot.hs
{-# LANGUAGE OverloadedStrings #-}
module Network.Wai.Auth.AppRoot
( smartAppRoot
) where
import Data.ByteString (ByteString)
import Data.CaseInsensitive (CI, mk)
import qualified Data.HashMap.Lazy as HM
import Data.Monoid ((<>))
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With)
import Data.Text.Encoding.Error (lenientDecode)
import Network.HTTP.Types (Header)
import Network.Wai (Request, isSecure, requestHeaderHost,
requestHeaders)
-- | Determine approot by:
--
-- * Respect the Host header and isSecure property, together with the following de facto standards: x-forwarded-protocol, x-forwarded-ssl, x-url-scheme, x-forwarded-proto, front-end-https. (Note: this list may be updated at will in the future without doc updates.)
--
-- Normally trusting headers in this way is insecure, however in the case of approot, the worst that can happen is that the client will get an incorrect URL. Note that this does not work for some situations, e.g.:
--
-- * Reverse proxies not setting one of the above mentioned headers
--
-- * Applications hosted somewhere besides the root of the domain name
--
-- * Reverse proxies that modify the host header
--
-- @since 0.1.0.0
smartAppRoot :: Request -> T.Text
smartAppRoot req =
let secure = isSecure req || any isSecureHeader (requestHeaders req)
host =
maybe "localhost" (decodeUtf8With lenientDecode) (requestHeaderHost req)
in (if secure
then "https://"
else "http://") <>
host
-- |
--
-- See: http://stackoverflow.com/a/16042648/369198
httpsHeaders :: HM.HashMap (CI ByteString) (CI ByteString)
httpsHeaders =
HM.fromList
[ ("X-Forwarded-Protocol", "https")
, ("X-Forwarded-Ssl", "on")
, ("X-Url-Scheme", "https")
, ("X-Forwarded-Proto", "https")
, ("Front-End-Https", "on")
]
isSecureHeader :: Header -> Bool
isSecureHeader (key, value) =
case HM.lookup key httpsHeaders of
Nothing -> False
Just value' -> valueCI == value'
where
valueCI = mk value