snap-1.0.0.0: src/Snap/Snaplet/Session/SecureCookie.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
------------------------------------------------------------------------------
-- | This is a support module meant to back all session back-end
-- implementations.
--
-- It gives us an encrypted and timestamped cookie that can store an arbitrary
-- serializable payload. For security, it will:
--
-- * Encrypt its payload together with a timestamp.
--
-- * Check the timestamp for session expiration everytime you read from the
-- cookie. This will limit intercept-and-replay attacks by disallowing
-- cookies older than the timeout threshold.
module Snap.Snaplet.Session.SecureCookie
( SecureCookie
, getSecureCookie
, setSecureCookie
, expireSecureCookie
-- ** Helper functions
, encodeSecureCookie
, decodeSecureCookie
, checkTimeout
) where
------------------------------------------------------------------------------
import Control.Monad
import Control.Monad.Trans
import Data.ByteString (ByteString)
import Data.Serialize
import Data.Time
import Data.Time.Clock.POSIX
import Snap.Core
import Web.ClientSession
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative
#endif
------------------------------------------------------------------------------
-- | Arbitrary payload with timestamp.
type SecureCookie t = (UTCTime, t)
------------------------------------------------------------------------------
-- | Get the cookie payload.
getSecureCookie :: (MonadSnap m, Serialize t)
=> ByteString -- ^ Cookie name
-> Key -- ^ Encryption key
-> Maybe Int -- ^ Timeout in seconds
-> m (Maybe t)
getSecureCookie name key timeout = do
rqCookie <- getCookie name
rspCookie <- getResponseCookie name <$> getResponse
let ck = rspCookie `mplus` rqCookie
let val = fmap cookieValue ck >>= decodeSecureCookie key
case val of
Nothing -> return Nothing
Just (ts, t) -> do
to <- checkTimeout timeout ts
return $ case to of
True -> Nothing
False -> Just t
------------------------------------------------------------------------------
-- | Decode secure cookie payload wih key.
decodeSecureCookie :: Serialize a
=> Key -- ^ Encryption key
-> ByteString -- ^ Encrypted payload
-> Maybe (SecureCookie a)
decodeSecureCookie key value = do
cv <- decrypt key value
(i, val) <- either (const Nothing) Just $ decode cv
return $ (posixSecondsToUTCTime (fromInteger i), val)
------------------------------------------------------------------------------
-- | Inject the payload.
setSecureCookie :: (MonadSnap m, Serialize t)
=> ByteString -- ^ Cookie name
-> Maybe ByteString -- ^ Cookie domain
-> Key -- ^ Encryption key
-> Maybe Int -- ^ Max age in seconds
-> t -- ^ Serializable payload
-> m ()
setSecureCookie name domain key to val = do
t <- liftIO getCurrentTime
val' <- encodeSecureCookie key (t, val)
let expire = to >>= Just . flip addUTCTime t . fromIntegral
let nc = Cookie name val' expire domain (Just "/") False True
modifyResponse $ addResponseCookie nc
------------------------------------------------------------------------------
-- | Encode SecureCookie with key into injectable payload
encodeSecureCookie :: (MonadIO m, Serialize t)
=> Key -- ^ Encryption key
-> SecureCookie t -- ^ Payload
-> m ByteString
encodeSecureCookie key (t, val) =
liftIO $ encryptIO key . encode $ (seconds, val)
where
seconds = round (utcTimeToPOSIXSeconds t) :: Integer
------------------------------------------------------------------------------
-- | Expire secure cookie
expireSecureCookie :: MonadSnap m
=> ByteString -- ^ Cookie name
-> Maybe ByteString -- ^ Cookie domain
-> m ()
expireSecureCookie name domain = expireCookie cookie
where
cookie = Cookie name "" Nothing domain (Just "/") False False
------------------------------------------------------------------------------
-- | Validate session against timeout policy.
--
-- * If timeout is set to 'Nothing', never trigger a time-out.
--
-- * Otherwise, do a regular time-out check based on current time and given
-- timestamp.
checkTimeout :: (MonadSnap m) => Maybe Int -> UTCTime -> m Bool
checkTimeout Nothing _ = return False
checkTimeout (Just x) t0 = do
t1 <- liftIO getCurrentTime
return $ t1 > addUTCTime (fromIntegral x) t0