-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | client-side session data -- -- uses the clientsession library to store session data in an HTTP cookie @package happstack-clientsession @version 7.1.0 -- | This module provides a simple session implementation which stores -- session data on the client as a cookie value. -- -- The cookie values stored in an encryted cookie to make it more -- difficult for users to tamper with the values. However, this does not -- prevent replay attacks, and should not be seen as a substitute for -- using HTTPS. Additionally, the cryptography libraries used to encrypt -- the cookie have never been audited. Hence you are encouraged to think -- carefully about what data you put in the session data. -- -- Another important thing to realize is clientside sessions do not -- provide Isolation. Imagine if the browser makes multiple simultaneous -- requests, which each modify the session data. The browser will submit -- the same cookie for each the requests, and each request handling -- thread will get their own copy of the session data. The threads will -- then modify their local copies independently and send their modified -- values back to the browser, overwriting each other. The final value -- will be determined by which ever request is sent last, and any changes -- made by the other request will be entirely lost. -- -- This means that clientsessions would not be suitable for implementing -- a request counter, because if overlapping requests are made, the count -- will be off. The count will only be accurate if the requests are -- processed sequentially. That said, the demo code implements a request -- counter anyway, because it is short and sweet. Also, this caveat was -- forgotten when the example code was being written. -- -- If you only modify the session data for POST requests, but not GET -- requests you are less likely to run into situations where you are -- losing changes, because there are not a lot of cases where a client -- will be submitting multiple POST requests in parallel. Though there is -- no guarantee. -- -- Alternatively, you can choose to only store data where it is ok -- if modifications are lost. For example, if the session data contains -- only a userid and the time of the last request they made, then there -- is no great loss if some of the modifications are lost, because the -- access times are going to all be about the same anyway. -- -- By default the client will need to submit the cookie that contains the -- client session data for every request (including images, and other -- static assets). So, storing a large amount of data in the client -- session will make requests slower and is not recommended. If you have -- assets which can be served with out examining the client session data -- you can use the sessionPath and sessionDomain parameters -- of SessionConf to limit when the browser sends the session data -- cookie. -- -- The first thing you need to do is enable some extensions which can be -- done via a LANGUAGE pragma at the top of your app: -- -- {-# LANGUAGE DeriveDataTypeable, TemplateHaskell #-} -- -- Then you will need some imports: -- --
-- module Main where -- -- import Happstack.Server (ServerPartT, Response, simpleHTTP -- , nullConf, nullDir, ok, toResponse -- ) -- import Happstack.Server.ClientSession -- ( ClientSession(..), ClientSessionT(..) -- , getDefaultKey, mkSessionConf -- , liftSessionStateT, withClientSessionT -- ) -- import Data.Data (Data, Typeable) -- import Data.Lens ((+=)) -- import Data.Lens.Template (makeLens) -- import Data.SafeCopy (base, deriveSafeCopy) ---- -- Next you will want to create a type to hold your session data. Here we -- use a simple record which we will update using data-lens-fd. -- But, you could also store a, Map Text Text, or whatever suits -- your fancy as long as it can be serialized. (So no data types that -- include functions, existential types, etc). -- --
-- data SessionData = SessionData
-- { _count :: Integer
-- }
-- deriving (Eq, Ord, Read, Show, Data, Typeable)
--
-- -- | here we make it a lens, but that is not required
-- $(makeLens ''SessionData)
--
--
-- We use the safecopy library to serialize the data so we can
-- encrypt it and store it in a cookie. safecopy provides
-- version migration, which means that we will be able to read-in old
-- session data if we change the data type. The easiest way to create a
-- SafeCopy instance is with deriveSafeCopy:
--
-- -- $(deriveSafeCopy 0 'base ''SessionData) ---- -- We also need to define what an emptySession looks like. This -- will be used for creating new sessions when the client does not -- already have one: -- --
-- instance ClientSession SessionData where
-- emptySession = SessionData { _count = 0 }
--
--
-- Next we have a function which reads a client-specific page counter and
-- returns the number of times the page has been reloaded.
--
-- In this function we use, liftSessionStateT to lift the
-- += lens function into ClientSessionT to increment and
-- return the value stored in the client session.
--
-- Alternatively, we could have used the getSession and
-- putSession functions from MonadClientSession. Those
-- functions do not require the use of liftSessionStateT.
--
-- -- routes :: ClientSessionT SessionData (ServerPartT IO) Response -- routes = -- do nullDir -- c <- liftSessionStateT $ count += 1 -- ok $ toResponse $ "you have viewed this page " ++ (show c) ++ " time(s)." ---- -- Finally, we unwrap the ClientSessionT monad transformer using -- withClientSessionT. -- -- The SessionConf type requires an encryption key. You can -- generate the key using getDefaultKey uses a default filename. -- Alternatively, you can specific the name you want to use explicitly -- using getKey. The key will be created automatically if it does -- not already exist. -- -- If you change the key, all existing client sessions will be -- invalidated. -- --
-- main :: IO () -- main = -- do key <- getDefaultKey -- let sessionConf = mkSessionConf key -- simpleHTTP nullConf $ withClientSessionT sessionConf $ routes ---- -- In a real application you might want to use a newtype wrapper -- around ClientSessionT to keep your type sigantures sane. An -- alternative version of this demo which does that can be found here: -- -- -- http://patch-tag.com/r/mae/happstack/snapshot/current/content/pretty/happstack-clientsession/demo/demo.hs module Happstack.Server.ClientSession -- | Your session type must have an instance for this class. class SafeCopy st => ClientSession st emptySession :: ClientSession st => st -- | Wrapper around the sessionData which tracks it state so we can avoid -- decoding or encoding/sending the cookie when not required data SessionStatus sessionData Unread :: SessionStatus sessionData NoChange :: sessionData -> SessionStatus sessionData Modified :: sessionData -> SessionStatus sessionData Expired :: SessionStatus sessionData -- | MonadClientSession provides the primary interface to get -- sessionData, put sessionData or expire -- sessionData. -- -- This is a class so you can use newtype deriving to make the functions -- available in your custom server monad. class MonadClientSession sessionData m | m -> sessionData getSession :: MonadClientSession sessionData m => m sessionData putSession :: MonadClientSession sessionData m => sessionData -> m () expireSession :: MonadClientSession sessionData m => m () -- | Configuration for the session cookie for passing to -- runClientSessionT or withClientSessionT. data SessionConf SessionConf :: String -> CookieLife -> Key -> String -> String -> Bool -> Bool -> SessionConf -- | Name of the cookie to hold your session data. sessionCookieName :: SessionConf -> String -- | Lifetime of that cookie. sessionCookieLife :: SessionConf -> CookieLife -- | Encryption key, usually from getKey or getDefaultKey. sessionKey :: SessionConf -> Key -- | cookie domain sessionDomain :: SessionConf -> String -- | cookie path sessionPath :: SessionConf -> String -- | Only use a session over secure transports. sessionSecure :: SessionConf -> Bool -- | Only use session over HTTP (to prevent it from being stolen via -- cross-site scripting) sessionHttpOnly :: SessionConf -> Bool -- | Create a SessionConf using defaults for everything except -- sessionKey. You can use record update syntax to override -- individual fields. -- --
-- main = do key <- getDefaultKey
-- let sessConf = (mkSessionConf key) { sessionCookieLife = oneWeek }
-- simpleHTTP nullConf $ withClientSessionT sessConf handlers
-- where
-- oneWeek = MaxAge $ 60 * 60 * 24 * 7
-- handlers = msum [...]
--
--
-- mkSessionConf is currently defined as:
--
--
-- mkSessionConf :: Key -> SessionConf
-- mkSessionConf key = SessionConf
-- { sessionCookieName = "Happstack.ClientSession"
-- , sessionCookieLife = Session
-- , sessionKey = key
-- , sessionDomain = ""
-- , sessionPath = "/"
-- , sessionSecure = False
-- , sessionHttpOnly = True
-- }
--
--
-- see also: getKey, getDefaultKey
mkSessionConf :: Key -> SessionConf
-- | ClientSessionT provides an environment in which we can access
-- and update the client-side session state
--
-- The inner monad needs to provide an instance of Happstack so
-- that the cookie value can be read and set. According
-- ClientSessionT must appear outside ServerPartT not
-- inside it.
newtype ClientSessionT sessionData m a
ClientSessionT :: ReaderT SessionConf (SessionStateT sessionData m) a -> ClientSessionT sessionData m a
unClientSessionT :: ClientSessionT sessionData m a -> ReaderT SessionConf (SessionStateT sessionData m) a
-- | transform the inner monad, but leave the session data alone.
mapClientSessionT :: (forall s. m (a, s) -> n (b, s)) -> ClientSessionT sessionData m a -> ClientSessionT sessionData n b
-- | run the ClientSessionT monad and get the result plus the final
-- SessionStatus sessionData
--
-- This function does not automatically update the cookie if the
-- session has been modified. It is up to you to do that. You probably
-- want to use withClientSessionT instead.
--
-- see also: withClientSessionT, mkSessionConf
runClientSessionT :: ClientSessionT sessionData m a -> SessionConf -> m (a, SessionStatus sessionData)
-- | Wrapper around your handlers that use the session.
--
-- This function automatically takes care of expiring or updating the
-- cookie if the expireSession or modifySession is
-- called.
--
-- If no changes are made to the session, then the cookie will not be
-- resent (because there is no need to).
withClientSessionT :: (Happstack m, Functor m, Monad m, FilterMonad Response m, ClientSession sessionData) => SessionConf -> ClientSessionT sessionData m a -> m a
-- | SessionStateT is like StateT, except it records if
-- put was ever called
data SessionStateT s m a
-- | Transform the inner monad. (similar to mapStateT)
--
-- The forall s. is to prevent you from modifying the session
-- state.
--
-- In theory we want this function to have the type:
--
-- -- mapSessionStateT :: (m a -> n b) -> SessionStateT s m a -> SessionStateT s n b ---- -- But that can not be done, so this is the next best thing. mapSessionStateT :: (forall s. m (a, s) -> n (b, s)) -> SessionStateT sessionData m a -> SessionStateT sessionData n b -- | lift a computation from the SessionStateT monad -- -- The primary purpose of this function is to make it possible to use the -- MonadState functions such as get and set to get -- and set the current session data. -- -- That makes it possible to use the MonadState based functions -- provided by Lens, e.g.: -- --
-- do c <- liftSessionStateT $ count += 1 --liftSessionStateT :: (Monad m, MonadTrans t, MonadClientSession sessionData (t m), Monad (t m)) => SessionStateT sessionData m a -> t m a -- | The keys used to store the cookies. We have an AES key used to encrypt -- the cookie and a Skein-MAC-512-256 key used verify the authencity and -- integrity of the cookie. The AES key needs to have exactly 32 bytes -- (256 bits) while Skein-MAC-512-256 should have 64 bytes (512 bits). -- -- See also getDefaultKey and initKey. data Key :: * -- | Get a key from the given text file. -- -- If the file does not exist or is corrupted a random key will be -- generated and stored in that file. getKey :: FilePath -> IO Key -- | Simply calls getKey defaultKeyFile. getDefaultKey :: IO Key instance Eq sessionData => Eq (SessionStatus sessionData) instance Ord sessionData => Ord (SessionStatus sessionData) instance Read sessionData => Read (SessionStatus sessionData) instance Show sessionData => Show (SessionStatus sessionData) instance Functor m => Functor (SessionStateT s m) instance (Monad m, Functor m) => Applicative (SessionStateT s m) instance (Functor m, MonadPlus m) => Alternative (SessionStateT s m) instance Monad m => Monad (SessionStateT s m) instance MonadPlus m => MonadPlus (SessionStateT s m) instance MonadBase b m => MonadBase b (SessionStateT s m) instance MonadIO m => MonadIO (SessionStateT s m) instance MonadFix m => MonadFix (SessionStateT s m) instance MonadError e m => MonadError e (SessionStateT s m) instance MonadCont m => MonadCont (SessionStateT s m) instance MonadTrans (SessionStateT s) instance (Monad m, HasRqData m) => HasRqData (SessionStateT s m) instance FilterMonad r m => FilterMonad r (SessionStateT s m) instance WebMonad r m => WebMonad r (SessionStateT s m) instance ServerMonad m => ServerMonad (SessionStateT s m) instance Functor m => Functor (ClientSessionT sessionData m) instance (Monad m, Functor m) => Applicative (ClientSessionT sessionData m) instance (Functor m, MonadPlus m) => Alternative (ClientSessionT sessionData m) instance Monad m => Monad (ClientSessionT sessionData m) instance MonadBase b m => MonadBase b (ClientSessionT sessionData m) instance MonadPlus m => MonadPlus (ClientSessionT sessionData m) instance MonadIO m => MonadIO (ClientSessionT sessionData m) instance MonadFix m => MonadFix (ClientSessionT sessionData m) instance MonadError e m => MonadError e (ClientSessionT sessionData m) instance MonadCont m => MonadCont (ClientSessionT sessionData m) instance (Monad m, HasRqData m) => HasRqData (ClientSessionT sessionData m) instance FilterMonad r m => FilterMonad r (ClientSessionT sessionData m) instance WebMonad r m => WebMonad r (ClientSessionT sessionData m) instance ServerMonad m => ServerMonad (ClientSessionT sessionData m) instance (Functor m, MonadPlus m, HasRqData m, ClientSession sessionData) => MonadClientSession sessionData (ClientSessionT sessionData m) instance MonadRWS r w s m => MonadRWS r w s (ClientSessionT sessionData m) instance MonadState s m => MonadState s (ClientSessionT sessionData m) instance MonadWriter w m => MonadWriter w (ClientSessionT sessionData m) instance MonadReader r m => MonadReader r (ClientSessionT sessionData m) instance MonadBaseControl b m => MonadBaseControl b (ClientSessionT s m) instance MonadTransControl (ClientSessionT s) instance MonadTrans (ClientSessionT sessionData) instance MonadPlus m => Monoid (ClientSessionT sessionData m a) instance Happstack m => Happstack (ClientSessionT sessionData m) instance MonadBaseControl b m => MonadBaseControl b (SessionStateT s m) instance MonadTransControl (SessionStateT s) instance (Monad m, ClientSession sessionData) => MonadState sessionData (SessionStateT sessionData m) instance MonadPlus m => Monoid (SessionStateT sessionData m a) instance Happstack m => Happstack (SessionStateT sessionData m)