module Network.Wai.Session (Session, SessionStore, withSession, genSessionId) where
import Data.Unique (newUnique, hashUnique)
import Data.Ratio (numerator, denominator)
import Data.Time.Clock.POSIX (getPOSIXTime)
import Data.String (fromString)
import Control.Monad.IO.Class (liftIO)
import Network.HTTP.Types (ResponseHeaders)
import Network.Wai (Middleware, Request(..))
#if MIN_VERSION_wai(3,0,0)
import Network.Wai.Internal (Response(ResponseBuilder,ResponseFile,ResponseStream,ResponseRaw))
#else
import Network.Wai.Internal (Response(ResponseBuilder,ResponseFile,ResponseSource))
#endif
import Web.Cookie (parseCookies, renderSetCookie, SetCookie(..))
#if MIN_VERSION_vault(0,3,0)
import Data.Vault.Lazy (Key)
import qualified Data.Vault.Lazy as Vault
#else
import Data.Vault (Key)
import qualified Data.Vault as Vault
#endif
import Data.ByteString (ByteString)
import qualified Blaze.ByteString.Builder as Builder
type Session m k v = ((k -> m (Maybe v)), (k -> v -> m ()))
type SessionStore m k v = (Maybe ByteString -> IO (Session m k v, IO ByteString))
withSession ::
SessionStore m k v
-> ByteString
-> SetCookie
-> Key (Session m k v)
-> Middleware
#if MIN_VERSION_wai(3,0,0)
withSession sessions cookieName cookieDefaults vkey app req respond = do
#else
withSession sessions cookieName cookieDefaults vkey app req = do
#endif
(session, getNewCookie) <- liftIO $ sessions $ lookup cookieName =<< cookies
#if MIN_VERSION_wai(3,0,0)
app (req {vault = Vault.insert vkey session (vault req)}) (\r -> do
newCookieVal <- liftIO getNewCookie
respond $ mapHeader (\hs -> (setCookie, newCookie newCookieVal):hs) r
)
#else
resp <- app (req {vault = Vault.insert vkey session (vault req)})
newCookieVal <- liftIO getNewCookie
return $ mapHeader (\hs -> (setCookie, newCookie newCookieVal):hs) resp
#endif
where
newCookie v = Builder.toByteString $ renderSetCookie $ cookieDefaults {
setCookieName = cookieName, setCookieValue = v
}
cookies = fmap parseCookies $ lookup ciCookie (requestHeaders req)
setCookie = fromString "Set-Cookie"
ciCookie = fromString "Cookie"
genSessionId :: IO ByteString
genSessionId = do
u <- fmap (toInteger . hashUnique) newUnique
time <- fmap toRational getPOSIXTime
return $ fromString $ show (numerator time * denominator time * u)
mapHeader :: (ResponseHeaders -> ResponseHeaders) -> Response -> Response
mapHeader f (ResponseFile s h b1 b2) = ResponseFile s (f h) b1 b2
mapHeader f (ResponseBuilder s h b) = ResponseBuilder s (f h) b
#if MIN_VERSION_wai(3,0,0)
mapHeader f (ResponseStream s h b) = ResponseStream s (f h) b
mapHeader _ (ResponseRaw _ _) = error "Cannot mapHeader of Wai.Interal.ResponseRaw when trying to add session cookie header"
#else
mapHeader f (ResponseSource s h b) = ResponseSource s (f h) b
#endif