2015-08-17 23:56:29 +02:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE DeriveGeneric #-}
|
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2015-04-24 14:00:57 +02:00
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
2015-08-17 23:56:29 +02:00
|
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
{-# LANGUAGE TypeOperators #-}
|
2015-09-10 08:49:19 +02:00
|
|
|
|
2015-08-17 23:56:29 +02:00
|
|
|
import Data.Aeson
|
|
|
|
import Data.ByteString (ByteString)
|
2016-01-14 23:43:48 +01:00
|
|
|
import Data.IORef
|
2015-08-17 23:56:29 +02:00
|
|
|
import Data.Text (Text)
|
|
|
|
import GHC.Generics
|
|
|
|
import Network.Wai
|
|
|
|
import Network.Wai.Handler.Warp
|
|
|
|
import Servant
|
|
|
|
import Servant.Server.Internal
|
2015-04-24 14:00:57 +02:00
|
|
|
|
|
|
|
-- Pretty much stolen/adapted from
|
|
|
|
-- https://github.com/haskell-servant/HaskellSGMeetup2015/blob/master/examples/authentication-combinator/AuthenticationCombinator.hs
|
|
|
|
|
2016-01-14 23:43:48 +01:00
|
|
|
type DBConnection = IORef [ByteString]
|
|
|
|
type DBLookup = DBConnection -> ByteString -> IO Bool
|
|
|
|
|
|
|
|
initDB :: IO DBConnection
|
|
|
|
initDB = newIORef ["good password"]
|
2015-04-24 14:00:57 +02:00
|
|
|
|
|
|
|
isGoodCookie :: DBLookup
|
2016-01-14 23:43:48 +01:00
|
|
|
isGoodCookie ref password = do
|
|
|
|
allowed <- readIORef ref
|
|
|
|
return (password `elem` allowed)
|
2015-04-24 14:00:57 +02:00
|
|
|
|
|
|
|
data AuthProtected
|
|
|
|
|
|
|
|
instance HasServer rest => HasServer (AuthProtected :> rest) where
|
2015-05-03 01:53:38 +02:00
|
|
|
type ServerT (AuthProtected :> rest) m = ServerT rest m
|
2016-01-14 23:43:48 +01:00
|
|
|
type HasConfig (AuthProtected :> rest) config =
|
|
|
|
(HasConfigEntry config DBConnection, HasConfig rest config)
|
2015-04-24 14:00:57 +02:00
|
|
|
|
2016-01-14 23:43:48 +01:00
|
|
|
route Proxy config subserver = WithRequest $ \ request ->
|
|
|
|
route (Proxy :: Proxy rest) config $ addAcceptCheck subserver $ cookieCheck request
|
2015-09-16 22:07:55 +02:00
|
|
|
where
|
|
|
|
cookieCheck req = case lookup "Cookie" (requestHeaders req) of
|
|
|
|
Nothing -> return $ FailFatal err401 { errBody = "Missing auth header" }
|
|
|
|
Just v -> do
|
2016-01-14 23:43:48 +01:00
|
|
|
let dbConnection = getConfigEntry config
|
|
|
|
authGranted <- isGoodCookie dbConnection v
|
2015-09-16 22:07:55 +02:00
|
|
|
if authGranted
|
|
|
|
then return $ Route ()
|
|
|
|
else return $ FailFatal err403 { errBody = "Invalid cookie" }
|
2015-04-24 14:00:57 +02:00
|
|
|
|
|
|
|
type PrivateAPI = Get '[JSON] [PrivateData]
|
|
|
|
|
|
|
|
type PublicAPI = Get '[JSON] [PublicData]
|
|
|
|
|
|
|
|
type API = "private" :> AuthProtected :> PrivateAPI
|
|
|
|
:<|> PublicAPI
|
|
|
|
|
|
|
|
newtype PrivateData = PrivateData { ssshhh :: Text }
|
|
|
|
deriving (Eq, Show, Generic)
|
|
|
|
|
|
|
|
instance ToJSON PrivateData
|
|
|
|
|
|
|
|
newtype PublicData = PublicData { somedata :: Text }
|
|
|
|
deriving (Eq, Show, Generic)
|
|
|
|
|
|
|
|
instance ToJSON PublicData
|
|
|
|
|
|
|
|
api :: Proxy API
|
|
|
|
api = Proxy
|
|
|
|
|
|
|
|
server :: Server API
|
|
|
|
server = return prvdata :<|> return pubdata
|
|
|
|
|
|
|
|
where prvdata = [PrivateData "this is a secret"]
|
|
|
|
pubdata = [PublicData "this is a public piece of data"]
|
|
|
|
|
|
|
|
main :: IO ()
|
2016-01-14 23:43:48 +01:00
|
|
|
main = do
|
|
|
|
dbConnection <- initDB
|
|
|
|
let config = dbConnection :. EmptyConfig
|
|
|
|
run 8080 (serve api config server)
|
2015-04-24 14:00:57 +02:00
|
|
|
|
|
|
|
{- Sample session:
|
|
|
|
$ curl http://localhost:8080/
|
|
|
|
[{"somedata":"this is a public piece of data"}]
|
|
|
|
$ curl http://localhost:8080/private
|
|
|
|
Missing auth header.
|
|
|
|
$ curl -H "Cookie: good password" http://localhost:8080/private
|
|
|
|
[{"ssshhh":"this is a secret"}]
|
|
|
|
$ curl -H "Cookie: bad password" http://localhost:8080/private
|
|
|
|
Invalid cookie.
|
2015-05-03 01:53:38 +02:00
|
|
|
-}
|