Make auth-combinator example much clearer

This commit is contained in:
aaron levin 2016-01-27 19:55:17 +01:00
parent 78da8e9a77
commit 61c5e05906
2 changed files with 32 additions and 16 deletions

View file

@ -13,32 +13,46 @@ import Control.Monad.Trans.Except (ExceptT, throwE)
import Data.Aeson hiding ((.:)) import Data.Aeson hiding ((.:))
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Data.Map (Map, fromList)
import qualified Data.Map as Map
import Data.Text (Text) import Data.Text (Text)
import GHC.Generics import GHC.Generics
import Network.Wai import Network.Wai
import Network.Wai.Handler.Warp import Network.Wai.Handler.Warp
import Servant import Servant
-- | This file contains an authenticated server using servant's generalized
-- authentication support. Our basic authentication scheme is trivial: we
-- look for a cookie named "servant-auth-cookie" and its value will contain
-- a key, which we use to lookup a User. Obviously this is an absurd example,
-- but we pick something simple and non-standard to show you how to extend
-- servant's support for authentication.
-- | A user type that we "fetch from the database" after -- | A user type that we "fetch from the database" after
-- performing authentication -- performing authentication
newtype User = User { unUser :: Text } newtype User = User { unUser :: Text }
-- | A (pure) database mapping keys to users.
database :: Map ByteString User
database = fromList [ ("key1", User "Anne Briggs")
, ("key2", User "Bruce Cockburn")
, ("key3", User "Ghédalia Tazartès")
]
-- | A method that, when given a password, will return a User. -- | A method that, when given a password, will return a User.
-- This is our bespoke (and bad) authentication logic. -- This is our bespoke (and bad) authentication logic.
lookupUser :: ByteString -> ExceptT ServantErr IO User lookupUser :: ByteString -> ExceptT ServantErr IO User
lookupUser cookie = lookupUser key = case Map.lookup key database of
if cookie == "good password" Nothing -> throwE (err403 { errBody = "Invalid Cookie" })
then return (User "user") Just usr -> return usr
else throwE (err403 { errBody = "Invalid Cookie" })
-- | The auth handler wraps a function from Request -> ExceptT ServantErr IO User -- | The auth handler wraps a function from Request -> ExceptT ServantErr IO User
-- we look for a Cookie and pass the value of the cookie to `lookupUser`. -- we look for a Cookie and pass the value of the cookie to `lookupUser`.
authHandler :: AuthHandler Request User authHandler :: AuthHandler Request User
authHandler = authHandler =
let handler req = case lookup "Cookie" (requestHeaders req) of let handler req = case lookup "servant-auth-cookie" (requestHeaders req) of
Nothing -> throwE (err401 { errBody = "Missing auth header" }) Nothing -> throwE (err401 { errBody = "Missing auth header" })
Just cookie -> lookupUser cookie Just authCookieKey -> lookupUser authCookieKey
in mkAuthHandler handler in mkAuthHandler handler
-- | Data types that will be returned from various api endpoints -- | Data types that will be returned from various api endpoints
@ -75,15 +89,16 @@ type instance AuthReturnType (AuthProtect "cookie-auth") = User
serverConfig :: Config (AuthHandler Request User ': '[]) serverConfig :: Config (AuthHandler Request User ': '[])
serverConfig = authHandler :. EmptyConfig serverConfig = authHandler :. EmptyConfig
-- | Our API, where we provide all the author-supplied handlers for each end point. -- | Our API, where we provide all the author-supplied handlers for each end
-- note that 'prvdata' is a function that takes 'User' as an argument. We dont' worry -- point. Note that 'privateDataFunc' is a function that takes 'User' as an
-- about the authentication instrumentation here, that is taken care of by supplying -- argument. We dont' worry about the authentication instrumentation here,
-- configuration -- that is taken care of by supplying configuration
server :: Server API server :: Server API
server = prvdata :<|> return pubdata server = privateDataFunc :<|> return publicData
where prvdata (User name) = return [PrivateData ("this is a secret: " <> name)] where privateDataFunc (User name) =
pubdata = [PublicData "this is a public piece of data"] return [PrivateData ("this is a secret: " <> name)]
publicData = [PublicData "this is a public piece of data"]
-- | run our server -- | run our server
main :: IO () main :: IO ()
@ -94,10 +109,10 @@ main = run 8080 (serve api serverConfig server)
$ curl -XGET localhost:8080/private $ curl -XGET localhost:8080/private
Missing auth header Missing auth header
$ curl -XGET localhost:8080/private -H "Cookie: good password" $ curl -XGET localhost:8080/private -H "servant-auth-cookie: key3"
[{"ssshhh":"this is a secret: user"}] [{"ssshhh":"this is a secret: Ghédalia Tazartès"}]
$ curl -XGET localhost:8080/private -H "Cookie: bad password" $ curl -XGET localhost:8080/private -H "servant-auth-cookie: bad-key"
Invalid Cookie Invalid Cookie
$ curl -XGET localhost:8080/public $ curl -XGET localhost:8080/public

View file

@ -96,6 +96,7 @@ executable auth-combinator
aeson >= 0.8 aeson >= 0.8
, base >= 4.7 && < 5 , base >= 4.7 && < 5
, bytestring , bytestring
, containers
, http-types , http-types
, servant == 0.5.* , servant == 0.5.*
, servant-server == 0.5.* , servant-server == 0.5.*