{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} module Yesod.Helpers.Auth2 ( Auth , AuthPlugin (..) , AuthRoute (..) , getAuth , Creds (..) , YesodAuth (..) , setCreds , maybeAuthId , maybeAuth , requireAuthId , requireAuth , authDummy ) where import Yesod import Language.Haskell.TH.Syntax hiding (lift) import qualified Data.ByteString.Char8 as S8 import qualified Network.Wai as W data Auth = Auth type Method = String type Piece = String data AuthPlugin m = AuthPlugin { apName :: String , apDispatch :: Method -> [Piece] -> GHandler Auth m () , apLogin :: forall s. (Route Auth -> Route m) -> GWidget s m () } getAuth :: a -> Auth getAuth = const Auth -- | User credentials data Creds m = Creds { credsPlugin :: String -- ^ How the user was authenticated , credsIdent :: String -- ^ Identifier. Exact meaning depends on plugin. , credsExtra :: [(String, String)] } class Yesod m => YesodAuth m where type AuthId m -- | Default destination on successful login, if no other -- destination exists. loginDest :: m -> Route m -- | Default destination on successful logout, if no other -- destination exists. logoutDest :: m -> Route m getAuthId :: Creds m -> GHandler s m (Maybe (AuthId m)) showAuthId :: m -> AuthId m -> String readAuthId :: m -> String -> Maybe (AuthId m) authPlugins :: [AuthPlugin m] mkYesodSub "Auth" [ ClassP ''YesodAuth [VarT $ mkName "master"] ] [$parseRoutes| /check CheckR GET /login LoginR GET /logout LogoutR GET POST /page/#String/*Strings PluginR |] credsKey :: String credsKey = "_ID" setCreds :: YesodAuth m => Bool -> Creds m -> GHandler s m () setCreds doRedirects creds = do y <- getYesod maid <- getAuthId creds case maid of Nothing -> if doRedirects then do case authRoute y of Nothing -> do rh <- defaultLayout $ addBody [$hamlet| %h1 Invalid login |] sendResponse rh Just ar -> do setMessage $ string "Invalid login" redirect RedirectTemporary ar else return () Just aid -> do setSession credsKey $ showAuthId y aid if doRedirects then do setMessage $ string "You are now logged in" redirect RedirectTemporary $ loginDest y else return () getCheckR :: YesodAuth m => GHandler Auth m RepHtmlJson getCheckR = do creds <- maybeAuthId defaultLayoutJson (do setTitle $ string "Authentication Status" addBody $ html creds) (json creds) where html creds = [$hamlet| %h1 Authentication Status $maybe creds _ %p Logged in. $nothing %p Not logged in. |] json creds = jsonMap [ ("logged_in", jsonScalar $ maybe "false" (const "true") creds) ] getLoginR :: YesodAuth m => GHandler Auth m RepHtml getLoginR = defaultLayout $ do setTitle $ string "Login" tm <- liftHandler getRouteToMaster mapM_ (flip apLogin tm) authPlugins getLogoutR :: YesodAuth m => GHandler Auth m () getLogoutR = postLogoutR -- FIXME redirect to post postLogoutR :: YesodAuth m => GHandler Auth m () postLogoutR = do y <- getYesod deleteSession credsKey redirectUltDest RedirectTemporary $ logoutDest y handlePluginR :: YesodAuth m => String -> [String] -> GHandler Auth m () handlePluginR plugin pieces = do env <- waiRequest let method = S8.unpack $ W.requestMethod env case filter (\x -> apName x == plugin) authPlugins of [] -> notFound ap:_ -> apDispatch ap method pieces -- | Retrieves user credentials, if user is authenticated. maybeAuthId :: YesodAuth m => GHandler s m (Maybe (AuthId m)) maybeAuthId = do ms <- lookupSession credsKey y <- getYesod case ms of Nothing -> return Nothing Just s -> return $ readAuthId y s maybeAuth :: ( YesodAuth m , Key val ~ AuthId m , PersistBackend (YesodDB m (GHandler s m)) , PersistEntity val , YesodPersist m ) => GHandler s m (Maybe (Key val, val)) maybeAuth = do maid <- maybeAuthId case maid of Nothing -> return Nothing Just aid -> do ma <- runDB $ get aid case ma of Nothing -> return Nothing Just a -> return $ Just (aid, a) requireAuthId :: YesodAuth m => GHandler s m (AuthId m) requireAuthId = maybeAuthId >>= maybe redirectLogin return requireAuth :: ( YesodAuth m , Key val ~ AuthId m , PersistBackend (YesodDB m (GHandler s m)) , PersistEntity val , YesodPersist m ) => GHandler s m (Key val, val) requireAuth = maybeAuth >>= maybe redirectLogin return redirectLogin :: Yesod m => GHandler s m a redirectLogin = do y <- getYesod setUltDest' case authRoute y of Just z -> redirect RedirectTemporary z Nothing -> permissionDenied "Please configure authRoute" authDummy :: YesodAuth m => AuthPlugin m authDummy = AuthPlugin "dummy" dispatch login where dispatch "POST" [] = do ident <- runFormPost' $ stringInput "ident" setCreds True $ Creds "dummy" ident [] dispatch _ _ = notFound url = PluginR "dummy" [] login authToMaster = do addBody [$hamlet| %form!method=post!action=@authToMaster.url@ Your new identifier is: $ %input!type=text!name=ident %input!type=submit!value="Dummy Login" |]