-- | -- Module: WebWire.Session -- Copyright: (c) 2011 Ertugrul Soeylemez -- License: BSD3 -- Maintainer: Ertugrul Soeylemez -- -- Reactive web session handling. module WebWire.Session ( -- * Sessions SessionCfg(..), WebSession, defSessionCfg, session, -- * Session ids getSessId, setNewSessId ) where import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString as B import Control.Arrow import Crypto.Random.AESCtr import Data.ByteString (ByteString) import Data.Time.Clock import FRP.NetWire import FRP.NetWire.Wire (mkGen) import WebWire.Tools import WebWire.Types -- | Session configuration. data SessionCfg = SessionCfg { -- | Validity duration of the session cookies. sessDuration :: Maybe NominalDiffTime, -- | Threshold of saved sessions, after which sessions can be -- deleted. sessThreshold :: Int, -- | Minimum validitity time. Younger sessions won't be killed on -- the server side. sessTimeLimit :: Time } -- | Session identifiers. type WebSession = ByteString -- | Default session configuration. defSessionCfg :: SessionCfg defSessionCfg = SessionCfg { sessDuration = Nothing, sessThreshold = 1000, sessTimeLimit = 7200 } -- | Generate a new session id at every instant. genSessId :: WebWire site a ByteString genSessId = mkGen $ \_ _ -> do prng' <- liftIO makeSystem let (str, prng) = genRandomBytes prng' 32 return (Right str, genSessId' prng) where genSessId' :: AESRNG -> WebWire site a ByteString genSessId' prng' = mkGen $ \_ _ -> let (str, prng) = genRandomBytes prng' 32 in return (Right str, genSessId' prng) -- | Get the current session id. Inhibits, if the client didn't have -- one. getSessId :: WebWire site (Maybe NominalDiffTime) WebSession getSessId = proc validity -> do sessIdEncoded <- getCookie -< "SESSION" let sessIdStr = B64.decodeLenient (B.take 64 sessIdEncoded) require_ -< B.length sessIdStr == 32 setCookieSimple -< ("SESSION", sessIdEncoded, validity) identity -< sessIdStr -- | Reactive session handling. The given wire is evolved for each user -- session individually. session :: SessionCfg -> WebWire site (WebSession, a) b -> WebWire site a b session scfg userWire = proc x' -> do sessId <- getSessId <+> setNewSessId -< sessDuration scfg contextLimited userWire -< (sessThreshold scfg, sessTimeLimit scfg, sessId, x') -- | Generate a new session id and sends a cookie to the client. The -- input signal specifies the validity duration. If 'Nothing', then the -- session is valid for the duration of the browser session. setNewSessId :: WebWire site (Maybe NominalDiffTime) ByteString setNewSessId = proc validity -> do sessId <- genSessId -< () setCookieSimple -< ("SESSION", B64.encode sessId, validity) identity -< sessId