module Web.Spock.SessionManager
( openSessionManager
, SessionId, Session(..), SessionManager(..)
)
where
import Web.Spock.Types
import Web.Spock.Cookie
import Control.Concurrent.STM
import Control.Monad.Trans
import Data.Time
import System.Random
import Web.Scotty.Trans
import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString.Base64 as B64
import qualified Data.HashMap.Strict as HM
import qualified Data.Text.Encoding as T
_COOKIE_NAME_ = "asession"
sessionTTL = 10 * 60 * 60
sessionIdEntropy = 12 :: Int
openSessionManager :: IO (SessionManager a)
openSessionManager =
do cacheHM <- atomically $ newTVar HM.empty
return $ SessionManager
{ sm_loadSession = loadSessionImpl cacheHM
, sm_sessionFromCookie = sessionFromCookieImpl cacheHM
, sm_createCookieSession = createCookieSessionImpl cacheHM
, sm_newSession = newSessionImpl cacheHM
, sm_deleteSession = deleteSessionImpl cacheHM
}
createCookieSessionImpl :: MonadIO m
=> UserSessions a
-> a
-> ActionT m ()
createCookieSessionImpl sessRef val =
do sess <- liftIO $ newSessionImpl sessRef val
setCookie' _COOKIE_NAME_ (sess_id sess) (sess_validUntil sess)
newSessionImpl :: UserSessions a
-> a
-> IO (Session a)
newSessionImpl sessionRef content =
do sess <- createSession content
atomically $ modifyTVar sessionRef (\hm -> HM.insert (sess_id sess) sess hm)
return sess
sessionFromCookieImpl :: MonadIO m
=> UserSessions a -> ActionT m (Maybe (Session a))
sessionFromCookieImpl sessionRef =
do mSid <- getCookie _COOKIE_NAME_
case mSid of
Just sid ->
liftIO $ loadSessionImpl sessionRef sid
Nothing ->
return Nothing
loadSessionImpl :: UserSessions a
-> SessionId
-> IO (Maybe (Session a))
loadSessionImpl sessionRef sid =
do sessHM <- atomically $ readTVar sessionRef
now <- getCurrentTime
case HM.lookup sid sessHM of
Just sess ->
do if addUTCTime sessionTTL (sess_validUntil sess) > now
then return $ Just sess
else do deleteSessionImpl sessionRef sid
return Nothing
Nothing ->
return Nothing
deleteSessionImpl :: UserSessions a
-> SessionId
-> IO ()
deleteSessionImpl sessionRef sid =
do atomically $ modifyTVar sessionRef (\hm -> HM.delete sid hm)
return ()
createSession :: a -> IO (Session a)
createSession content =
do gen <- g
let sid = T.decodeUtf8 $ B64.encode $ BSC.pack $
take sessionIdEntropy $ randoms gen
now <- getCurrentTime
let validUntil = addUTCTime sessionTTL now
return (Session sid validUntil content)
where
g = newStdGen :: IO StdGen