eved-0.0.1.0: src/Web/Eved/Auth.hs
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Web.Eved.Auth
where
import qualified Data.ByteString as BS
import Data.List.NonEmpty (NonEmpty (..), nonEmpty)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import qualified Network.HTTP.Client as HTTP
import Network.HTTP.Types (hAuthorization, unauthorized401)
import qualified Network.Wai as Wai
import qualified Web.Eved.Client as Client
import Web.Eved.Internal
import qualified Web.Eved.Server as Server
auth :: (Eved api m, EvedAuth api, Applicative f)
=> NonEmpty (f (AuthScheme a)) -> f (api b) -> f (api (a -> b))
auth schemes next = auth_ <$> sequenceA schemes <*> next
class EvedAuth api where
auth_ :: NonEmpty (AuthScheme a) -> api b -> api (a -> b)
data AuthResult a
= AuthSuccess a
| AuthFailure
| AuthNeeded
data AuthScheme a = AuthScheme
{ authenticateRequest :: Wai.Request -> AuthResult a
, addCredentials :: a -> HTTP.Request -> HTTP.Request
}
data BasicAuth = BasicAuth
{ basicAuthUsername :: Text
, basicAuthPassword :: Text
}
basicAuth :: AuthScheme BasicAuth
basicAuth = AuthScheme
{ authenticateRequest = \req ->
case lookup hAuthorization $ Wai.requestHeaders req of
Just authHeader ->
let (authType, rest) = T.breakOn " " $ decodeUtf8 authHeader
in
if T.toLower authType == "basic" then
let (username, rest') = T.breakOn ":" $ T.strip rest
password = T.drop 1 rest'
in AuthSuccess (BasicAuth username password)
else
AuthNeeded
Nothing ->
AuthNeeded
, addCredentials = \creds ->
HTTP.applyBasicAuth
(encodeUtf8 $ basicAuthUsername creds)
(encodeUtf8 $ basicAuthPassword creds)
}
instance EvedAuth Client.EvedClient where
auth_ (scheme :| _) next = Client.EvedClient $ \req a ->
Client.client next $ addCredentials scheme a req
instance EvedAuth (Server.EvedServerT m) where
auth_ schemes next = Server.EvedServerT $ \nt path action req resp ->
case go req schemes of
AuthSuccess a -> Server.unEvedServerT next nt path (fmap ($ a) action) req resp
_ -> resp $ Wai.responseLBS unauthorized401 [] "Unauthorized"
where
go request (s :| rest) =
case authenticateRequest s request of
AuthSuccess a -> AuthSuccess a
AuthFailure -> AuthFailure
AuthNeeded -> maybe AuthFailure (go request) (nonEmpty rest)