packages feed

wai-session-redis-0.1.0.3: src/Network/Wai/Session/Redis.hs

{-# LANGUAGE BlockArguments    #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

-- |
-- Module: Network.Wai.Session.Redis
-- Copyright: (c) 2021, t4ccer
-- License: BSD3
-- Stability: experimental
-- Portability: portable
--
-- Simple Redis backed wai-session backend. This module allows you to store
-- session data of wai-sessions in a Redis database.
module Network.Wai.Session.Redis
  ( dbStore
  , clearSession
  , SessionSettings(..)
  ) where

import           Control.Monad
import           Control.Monad.IO.Class
import           Data.ByteString        (ByteString)
import           Data.Default
import           Data.Either
import           Data.Serialize         (Serialize, decode, encode)
import           Database.Redis         hiding (decode)
import           Network.Wai.Session

-- | Settings to control session store
data SessionSettings = SessionSettings
  { redisConnectionInfo :: ConnectInfo
  , expiratinTime       :: Integer
  -- ^ Session expiration time in seconds
  }

instance Default SessionSettings where
  def = SessionSettings
    { redisConnectionInfo = defaultConnectInfo
    , expiratinTime       = 60*60*24*7 -- One week
    }

eitherToMaybe :: Either a b -> Maybe b
eitherToMaybe (Left _)  = Nothing
eitherToMaybe (Right a) = Just a

connectAndRunRedis :: MonadIO m => ConnectInfo -> Redis b -> m b
connectAndRunRedis ci cmd = liftIO do
  conn <- connect ci
  res  <- runRedis conn cmd
  disconnect conn
  return res

connectAndRunRedis_ :: MonadIO m => ConnectInfo -> Redis b -> m ()
connectAndRunRedis_ ci = void . connectAndRunRedis ci

createSession :: MonadIO m => SessionSettings -> m ByteString
createSession SessionSettings{..} = liftIO do
  sesId <- genSessionId
  connectAndRunRedis redisConnectionInfo $ do
    hset sesId "" ""
    expire sesId expiratinTime
  return sesId

isSesIdValid :: MonadIO m => SessionSettings -> ByteString -> m Bool
isSesIdValid SessionSettings{..} sesId = liftIO do
  res <- connectAndRunRedis redisConnectionInfo $
    exists sesId
  return $ fromRight False res

insertIntoSession :: MonadIO m => SessionSettings
  -> ByteString -- ^ Sessionn id
  -> ByteString -- ^ Key
  -> ByteString -- ^ Value
  -> m ()
insertIntoSession SessionSettings{..} sesId key value =
  connectAndRunRedis_ redisConnectionInfo $ do
  hset sesId key value
  expire sesId expiratinTime

lookupFromSession :: MonadIO m => SessionSettings
  -> ByteString -- ^ Session id
  -> ByteString -- ^ Key
  -> m (Maybe ByteString)
lookupFromSession SessionSettings{..} sesId key = do
  v <- connectAndRunRedis redisConnectionInfo $ do
    v <- hget sesId key
    expire sesId expiratinTime
    return v
  return $ join $ eitherToMaybe v

-- | Invalidate session id
clearSession :: MonadIO m => SessionSettings
  -> ByteString -- ^ Session id
  -> m ()
clearSession SessionSettings{..} sessionId =
  connectAndRunRedis_ redisConnectionInfo $
  del [sessionId]

-- | Create new redis backend wai session store
dbStore :: (MonadIO m1, MonadIO m2, Eq k, Serialize k, Serialize v) => SessionSettings -> m2 (SessionStore m1 k v)
dbStore s =
  return $ dbStore' s

dbStore' :: (MonadIO m1, MonadIO m2, Eq k, Serialize k, Serialize v, Monad m2) => SessionSettings -> Maybe ByteString -> m2 (Session m1 k v, m2 ByteString)
dbStore' s (Just sesId) = do
  isValid <- isSesIdValid s sesId
  if isValid
    then return (mkSessionFromSesId s sesId, return sesId)
    else dbStore' s Nothing
dbStore' s Nothing = do
  sesId <- createSession s
  return (mkSessionFromSesId s sesId, return sesId)

mkSessionFromSesId :: (MonadIO m1, Eq k, Serialize k, Serialize v) => SessionSettings -> ByteString -> Session m1 k v
mkSessionFromSesId s sesId = (mkLookup, mkInsert)
  where
    mkLookup k = liftIO (((eitherToMaybe . decode) =<<) <$> lookupFromSession s sesId (encode k))
    mkInsert k v = liftIO $ insertIntoSession s sesId (encode k) (encode v)