packages feed

happstack-clientsession (empty) → 7.0.0

raw patch · 4 files changed

+211/−0 lines, 4 filesdep +basedep +bytestringdep +cerealsetup-changed

Dependencies added: base, bytestring, cereal, clientsession, happstack-server, mtl, safecopy

Files

+ COPYING view
@@ -0,0 +1,29 @@+Copyright (c) 2012 Dag Odenhall+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++Redistributions of source code must retain the above copyright+notice, this list of conditions and the following disclaimer.++Redistributions in binary form must reproduce the above copyright+notice, this list of conditions and the following disclaimer in the+documentation and/or other materials provided with the distribution.++Neither the name of the HAppS.org; nor the names of its contributors+may be used to endorse or promote products derived from this software+without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+import Distribution.Simple+main = defaultMainWithHooks simpleUserHooks
+ happstack-clientsession.cabal view
@@ -0,0 +1,32 @@+Name          : happstack-clientsession+Version       : 7.0.0+License       : BSD3+License-file  : COPYING+Synopsis      : client-side session data+Description   : uses the clientsession library to store session data in an HTTP cookie+Author        : Dag Odenhall+Maintainer    : Happstack team <happs@googlegroups.com>+Homepage      : http://happstack.com+Cabal-Version : >= 1.6+Category      : Web, Happstack+Build-Type    : Simple++source-repository head+    type:     darcs+    subdir:   happstack-clientsession+    location: http://patch-tag.com/r/mae/happstack+++Library+  GHC-Options     : -Wall+  Hs-Source-Dirs  : src+  Exposed-Modules :+    Happstack.Server.ClientSession+  Build-Depends   :+    base             == 4.*,+    bytestring       == 0.9.*,+    cereal           == 0.3.*,+    clientsession    == 0.7.*,+    happstack-server == 7.0.*,+    mtl              == 2.0.*,+    safecopy         == 0.6.*
+ src/Happstack/Server/ClientSession.hs view
@@ -0,0 +1,147 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RecordWildCards #-}++module Happstack.Server.ClientSession+  ( ClientSession(..)+  , SessionConf(..)+  , mkSessionConf+  , ClientSessionT+  , runClientSessionT+  , getSession+  , putSession+  , expireSession+  , withSession+  , sessionPart+  ) where++import Control.Applicative   (Applicative, Alternative, optional)+import Control.Monad         (MonadPlus, when)+import Control.Monad.Reader  (ReaderT, runReaderT, ask, asks)+import Control.Monad.State   (StateT, State, evalStateT, runState, get, put)+import Control.Monad.Trans   (MonadIO, liftIO)+import Data.ByteString.Char8 (pack, unpack)+import Data.SafeCopy         (SafeCopy, safeGet, safePut)+import Data.Serialize        (runGet, runPut)+import Happstack.Server      (HasRqData, FilterMonad, WebMonad, ServerMonad, Happstack, Response, CookieLife(Session), Cookie(secure), lookCookieValue, addCookie, mkCookie, expireCookie)+import Web.ClientSession     (Key, decrypt, encryptIO)++-- | Your session type must have an instance for this class.+class SafeCopy st => ClientSession st where+  -- | An empty session, i.e. what you get when there is no existing+  -- session stored.+  empty :: st++data SessionState st = Encoded | Decoded st | Modified st | Expired++-- | Configuration for the session cookie for passing to 'runClientSessionT'.+data SessionConf = SessionConf+    { sessionCookieName :: String      -- ^ Name of the cookie to hold your session data.+    , sessionCookieLife :: CookieLife  -- ^ Lifetime of that cookie.+    , sessionKey        :: Key         -- ^ Encryption key, usually from 'getKey' or 'getDefaultKey'.+    , sessionSecure     :: Bool        -- ^ Only use a session over secure transports.+    }++-- | 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 $ runClientSessionT handlers sessConf+-- >   where+-- >     oneWeek  = MaxAge $ 60 * 60 * 24 * 7+-- >     handlers = sessionPart $ msum [...]+mkSessionConf :: Key -> SessionConf+mkSessionConf key = SessionConf+    { sessionCookieName = "Happstack.ClientSession"+    , sessionCookieLife = Session+    , sessionKey        = key+    , sessionSecure     = True+    }++-- | Transformer for monads capable of using a session.+newtype ClientSessionT st m a =+    ClientSessionT { unClientSessionT :: ReaderT SessionConf (StateT (SessionState st) m) a }+  deriving ( Functor, Applicative, Alternative+           , Monad, MonadIO, MonadPlus+           , HasRqData, FilterMonad r, WebMonad r, ServerMonad+           )++instance Happstack m => Happstack (ClientSessionT st m)++-- | Get the inner monad of a 'ClientSessionT'.+runClientSessionT :: Monad m => ClientSessionT st m a -> SessionConf -> m a+runClientSessionT cs sc =+    evalStateT (runReaderT (unClientSessionT cs) sc) Encoded++askCS :: Monad m => ClientSessionT st m SessionConf+askCS = ClientSessionT ask++asksCS :: Monad m => (SessionConf -> a) -> ClientSessionT st m a+asksCS = ClientSessionT . asks++getCS :: Monad m => ClientSessionT st m (SessionState st)+getCS = ClientSessionT get++putCS :: Monad m => SessionState st -> ClientSessionT st m ()+putCS = ClientSessionT . put++-- | Get the session value.  If the cookie has not been decoded yet, it+-- will be decoded.  If no session data is stored or the session has been+-- expired, 'empty' is returned.+getSession :: (Functor m, MonadPlus m, HasRqData m, ClientSession st)+           => ClientSessionT st m st+getSession = do+    ss <- getCS+    case ss of+      Decoded a  -> return a+      Modified a -> return a+      Expired    -> new+      Encoded    -> do a <- getValue+                       putCS $ Decoded a+                       return a+  where+    new      = return empty+    getValue = do name <- asksCS sessionCookieName+                  value <- optional $ lookCookieValue name+                  maybe new decode value+    decode v = do key <- asksCS sessionKey+                  maybe new (either (const new) return . runGet safeGet)+                    . decrypt key $ pack v++-- | Put a new value in the session.+putSession :: (Monad m, ClientSession st) => st -> ClientSessionT st m ()+putSession = putCS . Modified++-- | Expire the session, i.e. the cookie holding it.+expireSession :: Monad m => ClientSessionT st m ()+expireSession = putCS Expired++-- | Run a 'State' monad with the session.+withSession :: (Functor m, MonadPlus m, HasRqData m, ClientSession st, Eq st)+            => State st a -> ClientSessionT st m a+withSession m = do s <- getSession+                   let (a,st) = runState m s+                   when (st /= s) $ putSession st+                   return a++-- | Wrapper around your handlers that use the session.  Takes care of+-- expiring the cookie of an expired session, or encrypting a modified+-- session into the cookie.+sessionPart :: (Functor m, Monad m, MonadIO m, FilterMonad Response m, ClientSession st)+            => ClientSessionT st m a -> ClientSessionT st m a+sessionPart part = do+    a  <- part+    ss <- getCS+    case ss of+      Modified st -> encode st+      Expired     -> expire+      _           -> return ()+    return a+  where+    encode st = do SessionConf{..} <- askCS+                   bytes <- liftIO . encryptIO sessionKey . runPut . safePut $ st+                   addCookie sessionCookieLife $ (mkCookie sessionCookieName $ unpack bytes) { secure = sessionSecure }+    expire    = do name <- asksCS sessionCookieName+                   expireCookie name