162 lines
5.4 KiB
Haskell
162 lines
5.4 KiB
Haskell
module Yesod.Internal.Session
|
|
( encodeClientSession
|
|
, encodeClientSessionOld
|
|
, decodeClientSession
|
|
, decodeClientSessionOld
|
|
, clientSessionDateCacher
|
|
, ClientSessionDateCache(..)
|
|
, BackendSession
|
|
, SaveSession
|
|
, SaveSessionOld
|
|
, SessionBackend(..)
|
|
) where
|
|
|
|
import Yesod.Internal (Header(..))
|
|
import qualified Web.ClientSession as CS
|
|
import Data.Int (Int64)
|
|
import Data.Serialize
|
|
import Data.Time
|
|
import Data.ByteString (ByteString)
|
|
import Control.Concurrent (forkIO, killThread, threadDelay)
|
|
import Control.Monad (forever, guard)
|
|
import Data.Text (Text, pack, unpack)
|
|
import Control.Arrow (first)
|
|
import Control.Applicative ((<$>))
|
|
|
|
import qualified Data.ByteString.Char8 as S8
|
|
import qualified Data.IORef as I
|
|
import qualified Network.Wai as W
|
|
|
|
type BackendSession = [(Text, S8.ByteString)]
|
|
|
|
type SaveSession = BackendSession -- ^ The session contents after running the handler
|
|
-> UTCTime -- FIXME remove this in the next major version bump
|
|
-> IO [Header]
|
|
|
|
type SaveSessionOld = BackendSession -- ^ The session contents after running the handler
|
|
-> UTCTime
|
|
-> IO [Header]
|
|
|
|
newtype SessionBackend master = SessionBackend
|
|
{ sbLoadSession :: master
|
|
-> W.Request
|
|
-> UTCTime -- FIXME remove this in the next major version bump
|
|
-> IO (BackendSession, SaveSession) -- ^ Return the session data and a function to save the session
|
|
}
|
|
|
|
encodeClientSession :: CS.Key
|
|
-> CS.IV
|
|
-> ClientSessionDateCache -- ^ expire time
|
|
-> ByteString -- ^ remote host
|
|
-> [(Text, ByteString)] -- ^ session
|
|
-> ByteString -- ^ cookie value
|
|
encodeClientSession key iv date rhost session' =
|
|
CS.encrypt key iv $ encode $ SessionCookie expires rhost session'
|
|
where expires = Right (csdcExpiresSerialized date)
|
|
|
|
decodeClientSession :: CS.Key
|
|
-> ClientSessionDateCache -- ^ current time
|
|
-> ByteString -- ^ remote host field
|
|
-> ByteString -- ^ cookie value
|
|
-> Maybe [(Text, ByteString)]
|
|
decodeClientSession key date rhost encrypted = do
|
|
decrypted <- CS.decrypt key encrypted
|
|
SessionCookie (Left expire) rhost' session' <-
|
|
either (const Nothing) Just $ decode decrypted
|
|
guard $ expire > csdcNow date
|
|
guard $ rhost' == rhost
|
|
return session'
|
|
|
|
data SessionCookie = SessionCookie (Either UTCTime ByteString) ByteString [(Text, ByteString)]
|
|
deriving (Show, Read)
|
|
instance Serialize SessionCookie where
|
|
put (SessionCookie a b c) = do
|
|
either putTime putByteString a
|
|
put b
|
|
put (map (first unpack) c)
|
|
get = do
|
|
a <- getTime
|
|
b <- get
|
|
c <- map (first pack) <$> get
|
|
return $ SessionCookie (Left a) b c
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
|
|
-- Mostly copied from Kazu's date-cache, but with modifications
|
|
-- that better suit our needs.
|
|
--
|
|
-- The cached date is updated every 10s, we don't need second
|
|
-- resolution for session expiration times.
|
|
|
|
data ClientSessionDateCache =
|
|
ClientSessionDateCache {
|
|
csdcNow :: !UTCTime
|
|
, csdcExpires :: !UTCTime
|
|
, csdcExpiresSerialized :: !ByteString
|
|
} deriving (Eq, Show)
|
|
|
|
clientSessionDateCacher ::
|
|
NominalDiffTime -- ^ Inactive session valitity.
|
|
-> IO (IO ClientSessionDateCache, IO ())
|
|
clientSessionDateCacher validity = do
|
|
ref <- getUpdated >>= I.newIORef
|
|
tid <- forkIO $ forever (doUpdate ref)
|
|
return $! (I.readIORef ref, killThread tid)
|
|
where
|
|
getUpdated = do
|
|
now <- getCurrentTime
|
|
let expires = validity `addUTCTime` now
|
|
expiresS = runPut (putTime expires)
|
|
return $! ClientSessionDateCache now expires expiresS
|
|
doUpdate ref = do
|
|
threadDelay 10000000 -- 10s
|
|
I.writeIORef ref =<< getUpdated
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
|
|
putTime :: Putter UTCTime
|
|
putTime (UTCTime d t) =
|
|
let d' = fromInteger $ toModifiedJulianDay d
|
|
t' = fromIntegral $ fromEnum (t / diffTimeScale)
|
|
in put (d' * posixDayLength_int64 + min posixDayLength_int64 t')
|
|
|
|
getTime :: Get UTCTime
|
|
getTime = do
|
|
val <- get
|
|
let (d, t) = val `divMod` posixDayLength_int64
|
|
d' = ModifiedJulianDay $! fromIntegral d
|
|
t' = fromIntegral t
|
|
d' `seq` t' `seq` return (UTCTime d' t')
|
|
|
|
posixDayLength_int64 :: Int64
|
|
posixDayLength_int64 = 86400
|
|
|
|
diffTimeScale :: DiffTime
|
|
diffTimeScale = 1e12
|
|
|
|
encodeClientSessionOld :: CS.Key
|
|
-> CS.IV
|
|
-> UTCTime -- ^ expire time
|
|
-> ByteString -- ^ remote host
|
|
-> [(Text, ByteString)] -- ^ session
|
|
-> ByteString -- ^ cookie value
|
|
encodeClientSessionOld key iv expire rhost session' =
|
|
CS.encrypt key iv $ encode $ SessionCookie (Left expire) rhost session'
|
|
|
|
decodeClientSessionOld :: CS.Key
|
|
-> UTCTime -- ^ current time
|
|
-> ByteString -- ^ remote host field
|
|
-> ByteString -- ^ cookie value
|
|
-> Maybe [(Text, ByteString)]
|
|
decodeClientSessionOld key now rhost encrypted = do
|
|
decrypted <- CS.decrypt key encrypted
|
|
SessionCookie (Left expire) rhost' session' <-
|
|
either (const Nothing) Just $ decode decrypted
|
|
guard $ expire > now
|
|
guard $ rhost' == rhost
|
|
return session'
|