    ------------------------------------------------------------------------------
    -- | Internal module exporting AuthManager implementation.
    --
    {-# LANGUAGE DeriveDataTypeable         #-}
    {-# LANGUAGE ExistentialQuantification  #-}
    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    {-# LANGUAGE OverloadedStrings          #-}
    {-# LANGUAGE RecordWildCards            #-}

    module Snap.Snaplet.Auth.AuthManager
      ( -- * AuthManager Datatype
        AuthManager(..)

        -- * Backend Typeclass
        , IAuthBackend(..)

        -- * Context-free Operations
        , buildAuthUser
      ) where

    ------------------------------------------------------------------------------
    import           Data.ByteString (ByteString)
    import           Data.Text (Text)
    import           Data.Time
    import           Web.ClientSession

    import           Snap.Snaplet
    import           Snap.Snaplet.Session
    import           Snap.Snaplet.Auth.Types

    ------------------------------------------------------------------------------
    -- | Creates a new user from a username and password.
    --
    buildAuthUser :: IAuthBackend r =>
                     r            -- ^ An auth backend
                  -> Text         -- ^ Username
                  -> ByteString   -- ^ Password
                  -> IO (Either AuthFailure AuthUser)
    buildAuthUser r unm pass = do
      now <- getCurrentTime
      let au = defAuthUser {
                  userLogin     = unm
                , userPassword  = Nothing
                , userCreatedAt = Just now
                , userUpdatedAt = Just now
                }
      au' <- setPassword au pass
      save r au'

    ------------------------------------------------------------------------------
    -- | All storage backends need to implement this typeclass
    --
    class IAuthBackend r where
      -- | Create or update the given 'AuthUser' record.  A 'userId' of Nothing
      -- indicates that a new user should be created, otherwise the user
      -- information for that userId should be updated.
      save                  :: r -> AuthUser -> IO (Either AuthFailure AuthUser)
      lookupByUserId        :: r -> UserId   -> IO (Maybe AuthUser)
      lookupByLogin         :: r -> Text     -> IO (Maybe AuthUser)
      lookupByRememberToken :: r -> Text     -> IO (Maybe AuthUser)
      destroy               :: r -> AuthUser -> IO ()

    ------------------------------------------------------------------------------
    -- | Abstract data type holding all necessary information for auth operation
    data AuthManager b = forall r. IAuthBackend r => AuthManager {
          backend               :: r
            -- ^ Storage back-end

        , session               :: SnapletLens b SessionManager
            -- ^ A lens pointer to a SessionManager

        , activeUser            :: Maybe AuthUser
            -- ^ A per-request logged-in user cache

        , minPasswdLen          :: Int
            -- ^ Password length range

        , rememberCookieName    :: ByteString
            -- ^ Cookie name for the remember token

        , rememberPeriod        :: Maybe Int
            -- ^ Remember period in seconds. Defaults to 2 weeks.

        , siteKey               :: Key
            -- ^ A unique encryption key used to encrypt remember cookie

        , lockout               :: Maybe (Int, NominalDiffTime)
            -- ^ Lockout after x tries, re-allow entry after y seconds

        , randomNumberGenerator :: RNG
            -- ^ Random number generator
        }

    instance IAuthBackend (AuthManager b) where
        save AuthManager{..} u = save backend u
        lookupByUserId AuthManager{..} u = lookupByUserId backend u
        lookupByLogin AuthManager{..} u = lookupByLogin backend u
        lookupByRememberToken AuthManager{..} u = lookupByRememberToken backend u
        destroy AuthManager{..} u = destroy backend u