module Web.Wheb.Plugins.Auth
(
login
, logout
, register
, getCurrentUser
, queryCurrentUser
, loginRequired
, authMiddleware
, AuthUser (..)
, AuthContainer (..)
, AuthApp (..)
, AuthState (..)
, AuthBackend (..)
, AuthError (..)
, UserKey
, Password
, PwHash
, makePwHash
, verifyPw
, getUserSessionKey
) where
import Control.Monad.Except (liftM, MonadError(throwError), MonadIO(..))
import Crypto.PasswordStore (makePassword, verifyPassword)
import Data.Text.Encoding as ES (decodeUtf8, encodeUtf8)
import Data.Text.Lazy as T (fromStrict, pack, Text, toStrict)
import Web.Wheb (getHandlerState, getWithApp, modifyHandlerState',
WhebError(Error403), WhebHandlerT, WhebMiddleware, WhebT)
import Web.Wheb.Plugins.Session (deleteSessionValue, getSessionValue', SessionApp, setSessionValue)
register :: (AuthApp a, MonadIO m) => AuthUser -> Password -> WhebT a b m (Either AuthError AuthUser)
register un pw = runWithContainer $ backendRegister un pw
login :: (AuthApp a, AuthState b, MonadIO m) => UserKey -> Password -> WhebT a b m (Either AuthError AuthUser)
login un pw = do
loginResult <- (runWithContainer $ backendLogin un pw)
case loginResult of
Right au@(AuthUser userKey) -> do
sessionKey <- getUserSessionKey
deleteSessionValue sessionKey
setSessionValue sessionKey userKey
authSetUser (Just au)
_ -> return ()
return loginResult
logout :: (AuthApp a, AuthState b, MonadIO m) => WhebT a b m ()
logout = (runWithContainer backendLogout) >> (authSetUser Nothing)
getCurrentUser :: (AuthState b, MonadIO m) => WhebT a b m (Maybe AuthUser)
getCurrentUser = liftM getAuthUser getHandlerState
queryCurrentUser :: (AuthApp a, MonadIO m) => WhebT a b m (Maybe AuthUser)
queryCurrentUser = getUserSessionKey >>=
getSessionValue' (T.pack "") >>=
(\uid -> runWithContainer $ backendGetUser uid)
loginRequired :: (AuthState b, MonadIO m) =>
WhebHandlerT a b m ->
WhebHandlerT a b m
loginRequired action = getCurrentUser >>=
(maybe (throwError Error403) (const action))
authMiddleware :: (AuthApp a, AuthState b, MonadIO m) => WhebMiddleware a b m
authMiddleware = do
cur <- queryCurrentUser
authSetUser cur
return Nothing
type UserKey = Text
type Password = Text
type PwHash = Text
data AuthError = DuplicateUsername | UserDoesNotExist | InvalidPassword
deriving (Show)
data AuthUser = AuthUser { uniqueUserKey :: UserKey } deriving (Show)
type PossibleUser = Maybe AuthUser
data AuthContainer = forall r. AuthBackend r => AuthContainer r
class SessionApp a => AuthApp a where
getAuthContainer :: a -> AuthContainer
class AuthState a where
getAuthUser :: a -> PossibleUser
modifyAuthUser :: (PossibleUser -> PossibleUser) -> a -> a
class AuthBackend c where
backendLogin :: (AuthApp a, MonadIO m) => SessionApp a => UserKey -> Password -> c -> WhebT a b m (Either AuthError AuthUser)
backendRegister :: (AuthApp a, MonadIO m) => AuthUser -> Password -> c -> WhebT a b m (Either AuthError AuthUser)
backendGetUser :: (AuthApp a, MonadIO m) => UserKey -> c -> WhebT a b m (Maybe AuthUser)
backendLogout :: (AuthApp a, MonadIO m) => c -> WhebT a b m ()
backendLogout _ = getUserSessionKey >>= deleteSessionValue
runWithContainer :: (AuthApp a, MonadIO m) =>
(forall r. AuthBackend r => r -> WhebT a s m b) ->
WhebT a s m b
runWithContainer f = do
AuthContainer authStore <- getWithApp getAuthContainer
f authStore
authSetUser :: (AuthApp a, AuthState b, MonadIO m) => PossibleUser -> WhebT a b m ()
authSetUser cur = modifyHandlerState' (modifyAuthUser (const cur))
getUserSessionKey :: (AuthApp a, MonadIO m) => WhebT a b m Text
getUserSessionKey = return $ T.pack "user-id"
makePwHash :: MonadIO m => Password -> WhebT a b m PwHash
makePwHash pw = liftM (T.fromStrict . ES.decodeUtf8) $
liftIO $ makePassword (ES.encodeUtf8 $ T.toStrict pw) 14
verifyPw :: Text -> Text -> Bool
verifyPw pw hash = verifyPassword (ES.encodeUtf8 $ T.toStrict pw)
(ES.encodeUtf8 $ T.toStrict hash)