darcsden-1.1: src/DarcsDen/State/Session.hs
module DarcsDen.State.Session where
import Control.Monad.Trans
import Database.Redis.Monad hiding (expireAt)
import Data.Time
import Data.Time.Clock.POSIX
import System.UUID.V4
import qualified Data.ByteString as BS
import qualified Control.Monad.Trans as MTL
import DarcsDen.State.Util
import DarcsDen.State.User (OAuthID)
import DarcsDen.Util (fromBS, toBS)
data Notification
= Success { nMessage :: String }
| Message { nMessage :: String }
| Warning { nMessage :: String }
deriving (Eq, Show)
data Session =
Session
{ sID :: BS.ByteString
, sExpire :: UTCTime
, sUser :: Maybe String
, sNotifications :: [Notification]
, sOAuthReg :: Maybe (Either String OAuthID) -- Nothing -> registration not in process
-- Just (Left xs) -> secret key to prevent csrf
-- Just (Right xid) -> oauth identity of the user
}
deriving (Eq, Show)
expireAt :: WithRedis m => String -> Int -> m (Reply Int)
expireAt k t = do
now <- MTL.liftIO getCurrentTime
let relative = diffUTCTime (posixSecondsToUTCTime (fromIntegral t)) now
expire k (ceiling relative)
getSession :: MonadIO m => BS.ByteString -> m (Maybe Session)
getSession sid = withRedis $ do
RBulk mexpire <- get (sessionKey' sid "expire")
case mexpire of
Just e -> do
RBulk user <- get (sessionKey' sid "user")
RBulk gr <- get (sessionKey' sid "oauthreg")
ws <- fmap (map Warning) $ getNotifications "warning"
ms <- fmap (map Message) $ getNotifications "message"
ss <- fmap (map Success) $ getNotifications "success"
return . Just $ Session
{ sID = sid
, sExpire = posixSecondsToUTCTime (fromIntegral (e :: Int))
, sUser = user
, sNotifications = concat [ws, ms, ss]
, sOAuthReg = fmap read gr
}
Nothing -> return Nothing
where
getNotifications t = do
RMulti res <- smembers (sessionKey' sid t)
case res of
Just mns ->
return $ map (\(RBulk (Just n)) -> n) mns
Nothing -> return []
newSession :: MonadIO m => m (Maybe Session)
newSession = do
ui <- liftIO uuid
now <- liftIO (getCurrentTime)
let s = Session
{ sID = toBS . filter (/= '-') . show $ ui
, sExpire = addUTCTime (60 * 60 * 24 * 30) now
, sUser = Nothing
, sNotifications = []
, sOAuthReg = Nothing
}
withRedis $ do
incrBy (sessionKey s "expire") (sessionExpire s)
expireAt (sessionKey s "expire") (sessionExpire s)
return (Just s)
deleteSession :: MonadIO m => Session -> m ()
deleteSession s = withRedis $ do
del (sessionKey s "expire")
del (sessionKey s "user")
del (sessionKey s "warning")
del (sessionKey s "message")
del (sessionKey s "success")
del (sessionKey s "oauthreg")
return ()
setUser :: MonadIO m => Maybe String -> Session -> m Session
setUser mn s = withRedis $ do
case mn of
Just n -> do
set key n
expireAt key (sessionExpire s)
Nothing -> del key
return (s { sUser = mn })
where key = sessionKey s "user"
setOAuthReg :: MonadIO m => Maybe (Either String OAuthID) -> Session -> m ()
setOAuthReg v s = withRedis $ do
case v of
Just n -> do
set key (show n)
expire key (24 * 60 * 60)
return ()
Nothing -> do
del key
return ()
where key = sessionKey s "oauthreg"
clearNotifications :: MonadIO m => Session -> m ()
clearNotifications s = withRedis $
mapM_ (del . sessionKey s)
["warning", "message", "success"]
notice :: MonadIO m => Notification -> Session -> m ()
notice n s = withRedis $ do
sadd (key n) (nMessage n)
expireAt (key n) (sessionExpire s)
return ()
where
key (Warning _) = sessionKey s "warning"
key (Message _) = sessionKey s "message"
key (Success _) = sessionKey s "success"
warn :: MonadIO m => String -> Session -> m ()
warn = notice . Warning
success :: MonadIO m => String -> Session -> m ()
success = notice . Success
message :: MonadIO m => String -> Session -> m ()
message = notice . Message
sessionKey :: Session -> String -> String
sessionKey = sessionKey' . sID
sessionKey' :: BS.ByteString -> String -> String
sessionKey' sid n = "session:" ++ fromBS sid ++ ":" ++ n
sessionExpire :: Session -> Int
sessionExpire = ceiling . utcTimeToPOSIXSeconds . sExpire