{-# LANGUAGE OverloadedStrings #-} {- | Module : Network.Wai.Session Copyright : (c) 2015 Athan Clark License : BSD-3 Maintainer : athan.clark@gmail.com Stability : experimental Portability : GHC A simple interface for storing and parsing session information into cookies, which slightly differs from the interface designed by Greg Weber. -} module Network.Wai.Session where import Data.Time import qualified Data.ByteString as BS import Blaze.ByteString.Builder (toByteString) import Data.Vault.Lazy (Key) import qualified Data.Vault.Lazy as V import Network.Wai.Trans import Network.HTTP.Types import Web.Cookie data SessionConfig m k v = SessionConfig { renderKey :: k -> BS.ByteString -- ^ serialize the key , renderVal :: v -> BS.ByteString -- ^ serialize the value , parseKey :: BS.ByteString -> Maybe k -- ^ parse the serialized key , parseVal :: BS.ByteString -> Maybe v -- ^ parse the serialized value , keyName :: BS.ByteString -- ^ name used as a cookie , valName :: BS.ByteString -- ^ name used as a cooke , expire :: Integer -- ^ expiration time in Seconds , newVal :: k -> v -> m (Maybe v) -- ^ method to getting another value - -- this could ping a nonce cache in @m@ -- for instance. , vaultVar :: Key k -- ^ The vault 'Data.Vault.Lazy.Key' used -- to store the /session/ key -- when 'newVal' is successful. } sessionMiddleware :: Monad m => SessionConfig m k v -> MiddlewareT m sessionMiddleware cfg app req respond = case parseSessionCookies cfg (requestHeaders req) of Nothing -> app req respond Just (key,val) -> do mVal <- newVal cfg key val case mVal of Nothing -> app req respond Just val' -> let f = mapResponseHeaders (++ renderSessionCookies cfg key val') req' = req {vault = V.insert (vaultVar cfg) key (vault req)} in app req' (respond . f) parseSessionCookies :: SessionConfig m k v -> RequestHeaders -> Maybe (k, v) parseSessionCookies cfg xs = do cookies <- parseCookies <$> lookup "Cookie" xs key <- parseKey cfg =<< lookup (keyName cfg) cookies val <- parseVal cfg =<< lookup (valName cfg) cookies return (key, val) renderSessionCookies :: SessionConfig m k v -> k -> v -> ResponseHeaders renderSessionCookies cfg key val = repeat "Set-Cookie" `zip` cookies where cookies = (toByteString . renderSetCookie) <$> [ def { setCookieName = keyName cfg , setCookieValue = renderKey cfg key , setCookieMaxAge = Just $ secondsToDiffTime (expire cfg) } , def { setCookieName = valName cfg , setCookieValue = renderVal cfg val , setCookieMaxAge = Just $ secondsToDiffTime (expire cfg) } ]