This commit is contained in:
Michael Snoyman 2017-12-13 14:39:59 +02:00
parent 61c887f501
commit aed10fc84a
No known key found for this signature in database
GPG Key ID: A048E8C057E86876
5 changed files with 55 additions and 43 deletions

View File

@ -47,9 +47,10 @@ module Yesod.Auth
, asHtml , asHtml
) where ) where
import Control.Applicative ((<$>))
import Control.Monad (when) import Control.Monad (when)
import Control.Monad.Trans.Maybe import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader (ReaderT)
import Control.Monad.IO.Unlift (withRunInIO)
import Yesod.Auth.Routes import Yesod.Auth.Routes
import Data.Aeson hiding (json) import Data.Aeson hiding (json)
@ -60,11 +61,11 @@ import qualified Data.Text as T
import qualified Data.HashMap.Lazy as Map import qualified Data.HashMap.Lazy as Map
import Data.Monoid (Endo) import Data.Monoid (Endo)
import Network.HTTP.Client (Manager, Request, withResponse, Response, BodyReader) import Network.HTTP.Client (Manager, Request, withResponse, Response, BodyReader)
import Network.HTTP.Client.TLS (getGlobalManager)
import qualified Network.Wai as W import qualified Network.Wai as W
import Yesod.Core import Yesod.Core
import Yesod.Core.Types (HandlerFor(..))
import Yesod.Persist import Yesod.Persist
import Yesod.Auth.Message (AuthMessage, defaultMessage) import Yesod.Auth.Message (AuthMessage, defaultMessage)
import qualified Yesod.Auth.Message as Msg import qualified Yesod.Auth.Message as Msg
@ -110,8 +111,8 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
type AuthId master type AuthId master
-- | specify the layout. Uses defaultLayout by default -- | specify the layout. Uses defaultLayout by default
authLayout :: WidgetFor master () -> HandlerFor master Html authLayout :: WidgetFor master () -> AuthHandler master Html
authLayout = defaultLayout authLayout = liftHandler . defaultLayout
-- | Default destination on successful login, if no other -- | Default destination on successful login, if no other
-- destination exists. -- destination exists.
@ -126,7 +127,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- Default implementation is in terms of @'getAuthId'@ -- Default implementation is in terms of @'getAuthId'@
-- --
-- Since: 1.4.4 -- Since: 1.4.4
authenticate :: Creds master -> HandlerFor master (AuthenticationResult master) authenticate :: Creds master -> AuthHandler master (AuthenticationResult master)
authenticate creds = do authenticate creds = do
muid <- getAuthId creds muid <- getAuthId creds
@ -136,7 +137,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- --
-- Default implementation is in terms of @'authenticate'@ -- Default implementation is in terms of @'authenticate'@
-- --
getAuthId :: Creds master -> HandlerFor master (Maybe (AuthId master)) getAuthId :: Creds master -> AuthHandler master (Maybe (AuthId master))
getAuthId creds = do getAuthId creds = do
auth <- authenticate creds auth <- authenticate creds
@ -191,15 +192,16 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- type. This allows backends to reuse persistent connections. If none of -- type. This allows backends to reuse persistent connections. If none of
-- the backends you're using use HTTP connections, you can safely return -- the backends you're using use HTTP connections, you can safely return
-- @error \"authHttpManager\"@ here. -- @error \"authHttpManager\"@ here.
authHttpManager :: master -> Manager authHttpManager :: master -> IO Manager
authHttpManager _ = getGlobalManager
-- | Called on a successful login. By default, calls -- | Called on a successful login. By default, calls
-- @addMessageI "success" NowLoggedIn@. -- @addMessageI "success" NowLoggedIn@.
onLogin :: HandlerFor master () onLogin :: AuthHandler master ()
onLogin = addMessageI "success" Msg.NowLoggedIn onLogin = addMessageI "success" Msg.NowLoggedIn
-- | Called on logout. By default, does nothing -- | Called on logout. By default, does nothing
onLogout :: HandlerFor master () onLogout :: AuthHandler master ()
onLogout = return () onLogout = return ()
-- | Retrieves user credentials, if user is authenticated. -- | Retrieves user credentials, if user is authenticated.
@ -211,16 +213,16 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- other than a browser. -- other than a browser.
-- --
-- Since 1.2.0 -- Since 1.2.0
maybeAuthId :: HandlerFor master (Maybe (AuthId master)) maybeAuthId :: AuthHandler master (Maybe (AuthId master))
default maybeAuthId default maybeAuthId
:: (YesodAuthPersist master, Typeable (AuthEntity master)) :: (YesodAuthPersist master, Typeable (AuthEntity master))
=> HandlerFor master (Maybe (AuthId master)) => AuthHandler master (Maybe (AuthId master))
maybeAuthId = defaultMaybeAuthId maybeAuthId = defaultMaybeAuthId
-- | Called on login error for HTTP requests. By default, calls -- | Called on login error for HTTP requests. By default, calls
-- @addMessage@ with "error" as status and redirects to @dest@. -- @addMessage@ with "error" as status and redirects to @dest@.
onErrorHtml :: Route master -> Text -> HandlerFor master Html onErrorHtml :: Route master -> Text -> AuthHandler master Html
onErrorHtml dest msg = do onErrorHtml dest msg = do
addMessage "error" $ toHtml msg addMessage "error" $ toHtml msg
fmap asHtml $ redirect dest fmap asHtml $ redirect dest
@ -230,10 +232,13 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- --
-- The HTTP 'Request' is given in case it is useful to change behavior based on inspecting the request. -- The HTTP 'Request' is given in case it is useful to change behavior based on inspecting the request.
-- This is an experimental API that is not broadly used throughout the yesod-auth code base -- This is an experimental API that is not broadly used throughout the yesod-auth code base
runHttpRequest :: Request -> (Response BodyReader -> HandlerFor master a) -> HandlerFor master a runHttpRequest :: (MonadSubHandler m, HandlerSite m ~ master, SubHandlerSite m ~ Auth)
=> Request
-> (Response BodyReader -> ReaderT (SubsiteData Auth master) (HandlerFor master) a)
-> m a
runHttpRequest req inner = do runHttpRequest req inner = do
man <- authHttpManager Control.Applicative.<$> getYesod man <- getYesod >>= liftIO . authHttpManager
HandlerFor $ \t -> withResponse req man $ \res -> unHandlerFor (inner res) t lift $ withRunInIO $ \run -> withResponse req man $ run . inner
{-# MINIMAL loginDest, logoutDest, (authenticate | getAuthId), authPlugins, authHttpManager #-} {-# MINIMAL loginDest, logoutDest, (authenticate | getAuthId), authPlugins, authHttpManager #-}
@ -254,7 +259,7 @@ credsKey = "_ID"
-- Since 1.1.2 -- Since 1.1.2
defaultMaybeAuthId defaultMaybeAuthId
:: (YesodAuthPersist master, Typeable (AuthEntity master)) :: (YesodAuthPersist master, Typeable (AuthEntity master))
=> HandlerFor master (Maybe (AuthId master)) => AuthHandler master (Maybe (AuthId master))
defaultMaybeAuthId = runMaybeT $ do defaultMaybeAuthId = runMaybeT $ do
s <- MaybeT $ lookupSession credsKey s <- MaybeT $ lookupSession credsKey
aid <- MaybeT $ return $ fromPathPiece s aid <- MaybeT $ return $ fromPathPiece s
@ -263,7 +268,7 @@ defaultMaybeAuthId = runMaybeT $ do
cachedAuth cachedAuth
:: (YesodAuthPersist master, Typeable (AuthEntity master)) :: (YesodAuthPersist master, Typeable (AuthEntity master))
=> AuthId master -> HandlerFor master (Maybe (AuthEntity master)) => AuthId master -> AuthHandler master (Maybe (AuthEntity master))
cachedAuth cachedAuth
= fmap unCachedMaybeAuth = fmap unCachedMaybeAuth
. cached . cached
@ -298,7 +303,7 @@ loginErrorMessageI dest msg = do
loginErrorMessageMasterI :: (YesodAuth master, RenderMessage master AuthMessage) loginErrorMessageMasterI :: (YesodAuth master, RenderMessage master AuthMessage)
=> Route master => Route master
-> AuthMessage -> AuthMessage
-> HandlerFor master TypedContent -> AuthHandler master TypedContent
loginErrorMessageMasterI dest msg = do loginErrorMessageMasterI dest msg = do
mr <- getMessageRender mr <- getMessageRender
loginErrorMessage dest (mr msg) loginErrorMessage dest (mr msg)
@ -308,10 +313,13 @@ loginErrorMessageMasterI dest msg = do
loginErrorMessage :: YesodAuth master loginErrorMessage :: YesodAuth master
=> Route master => Route master
-> Text -> Text
-> HandlerFor master TypedContent -> AuthHandler master TypedContent
loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg) loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg)
messageJson401 :: Text -> HandlerFor master Html -> HandlerFor master TypedContent messageJson401 :: (MonadSubHandler m, HandlerSite m ~ master, SubHandlerSite m ~ Auth)
=> Text
-> m Html
-> m TypedContent
messageJson401 = messageJsonStatus unauthorized401 messageJson401 = messageJsonStatus unauthorized401
messageJson500 :: Text -> HandlerFor master Html -> HandlerFor master TypedContent messageJson500 :: Text -> HandlerFor master Html -> HandlerFor master TypedContent
@ -577,8 +585,8 @@ data AuthException = InvalidFacebookResponse
deriving (Show, Typeable) deriving (Show, Typeable)
instance Exception AuthException instance Exception AuthException
-- FIXME this is ugly, and I probably want to ditch the MonadSubHandler typeclass anyway -- FIXME HandlerSite m ~ SubHandlerSite m should be unnecessary
instance (YesodAuth (HandlerSite m), MonadSubHandler m) => YesodSubDispatch Auth m where instance (YesodAuth (HandlerSite m), HandlerSite m ~ SubHandlerSite m, MonadSubHandler m) => YesodSubDispatch Auth m where
yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth) yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth)
asHtml :: Html -> Html asHtml :: Html -> Html

View File

@ -1,5 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
-- | Provides a dummy authentication module that simply lets a user specify -- | Provides a dummy authentication module that simply lets a user specify
-- his/her identifier. This is not intended for real world use, just for -- his/her identifier. This is not intended for real world use, just for
-- testing. -- testing.
@ -15,9 +16,9 @@ authDummy :: YesodAuth m => AuthPlugin m
authDummy = authDummy =
AuthPlugin "dummy" dispatch login AuthPlugin "dummy" dispatch login
where where
dispatch "POST" [] = do dispatch "POST" [] = liftHandler $ do
ident <- lift $ runInputPost $ ireq textField "ident" ident <- runInputPost $ ireq textField "ident"
lift $ setCredsRedirect $ Creds "dummy" ident [] setCredsRedirect $ Creds "dummy" ident []
dispatch _ _ = notFound dispatch _ _ = notFound
url = PluginR "dummy" [] url = PluginR "dummy" []
login authToMaster = do login authToMaster = do

View File

@ -325,7 +325,7 @@ class ( YesodAuth site
-- Default: 'defaultRegisterHandler'. -- Default: 'defaultRegisterHandler'.
-- --
-- @since: 1.2.6 -- @since: 1.2.6
registerHandler :: HandlerT Auth (HandlerT site IO) Html registerHandler :: AuthHandler site Html
registerHandler = defaultRegisterHandler registerHandler = defaultRegisterHandler
-- | Handler called to render the \"forgot password\" page. -- | Handler called to render the \"forgot password\" page.
@ -335,7 +335,7 @@ class ( YesodAuth site
-- Default: 'defaultForgotPasswordHandler'. -- Default: 'defaultForgotPasswordHandler'.
-- --
-- @since: 1.2.6 -- @since: 1.2.6
forgotPasswordHandler :: HandlerT Auth (HandlerT site IO) Html forgotPasswordHandler :: AuthHandler site Html
forgotPasswordHandler = defaultForgotPasswordHandler forgotPasswordHandler = defaultForgotPasswordHandler
-- | Handler called to render the \"set password\" page. The -- | Handler called to render the \"set password\" page. The
@ -351,7 +351,7 @@ class ( YesodAuth site
-- field for the old password should be presented. -- field for the old password should be presented.
-- Otherwise, just two fields for the new password are -- Otherwise, just two fields for the new password are
-- needed. -- needed.
-> HandlerT Auth (HandlerT site IO) TypedContent -> AuthHandler site TypedContent
setPasswordHandler = defaultSetPasswordHandler setPasswordHandler = defaultSetPasswordHandler
authEmail :: (YesodAuthEmail m) => AuthPlugin m authEmail :: (YesodAuthEmail m) => AuthPlugin m
@ -371,7 +371,7 @@ authEmail =
dispatch "POST" ["set-password"] = postPasswordR >>= sendResponse dispatch "POST" ["set-password"] = postPasswordR >>= sendResponse
dispatch _ _ = notFound dispatch _ _ = notFound
getRegisterR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html getRegisterR :: YesodAuthEmail master => AuthHandler master Html
getRegisterR = registerHandler getRegisterR = registerHandler
-- | Default implementation of 'emailLoginHandler'. -- | Default implementation of 'emailLoginHandler'.
@ -437,7 +437,7 @@ defaultEmailLoginHandler toParent = do
-- | Default implementation of 'registerHandler'. -- | Default implementation of 'registerHandler'.
-- --
-- @since 1.2.6 -- @since 1.2.6
defaultRegisterHandler :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html defaultRegisterHandler :: YesodAuthEmail master => AuthHandler master Html
defaultRegisterHandler = do defaultRegisterHandler = do
(widget, enctype) <- lift $ generateFormPost registrationForm (widget, enctype) <- lift $ generateFormPost registrationForm
toParentRoute <- getRouteToParent toParentRoute <- getRouteToParent
@ -480,7 +480,7 @@ parseEmail = withObject "email" (\obj -> do
registerHelper :: YesodAuthEmail master registerHelper :: YesodAuthEmail master
=> Bool -- ^ allow usernames? => Bool -- ^ allow usernames?
-> Route Auth -> Route Auth
-> HandlerT Auth (HandlerT master IO) TypedContent -> AuthHandler master TypedContent
registerHelper allowUsername dest = do registerHelper allowUsername dest = do
y <- lift getYesod y <- lift getYesod
checkCsrfHeaderOrParam defaultCsrfHeaderName defaultCsrfParamName checkCsrfHeaderOrParam defaultCsrfHeaderName defaultCsrfParamName
@ -525,16 +525,16 @@ registerHelper allowUsername dest = do
lift $ sendVerifyEmail email verKey verUrl lift $ sendVerifyEmail email verKey verUrl
lift $ confirmationEmailSentResponse identifier lift $ confirmationEmailSentResponse identifier
postRegisterR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent postRegisterR :: YesodAuthEmail master => AuthHandler master TypedContent
postRegisterR = registerHelper False registerR postRegisterR = registerHelper False registerR
getForgotPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html getForgotPasswordR :: YesodAuthEmail master => AuthHandler master Html
getForgotPasswordR = forgotPasswordHandler getForgotPasswordR = forgotPasswordHandler
-- | Default implementation of 'forgotPasswordHandler'. -- | Default implementation of 'forgotPasswordHandler'.
-- --
-- @since 1.2.6 -- @since 1.2.6
defaultForgotPasswordHandler :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html defaultForgotPasswordHandler :: YesodAuthEmail master => AuthHandler master Html
defaultForgotPasswordHandler = do defaultForgotPasswordHandler = do
(widget, enctype) <- lift $ generateFormPost forgotPasswordForm (widget, enctype) <- lift $ generateFormPost forgotPasswordForm
toParent <- getRouteToParent toParent <- getRouteToParent
@ -569,13 +569,13 @@ defaultForgotPasswordHandler = do
fsAttrs = [("autofocus", "")] fsAttrs = [("autofocus", "")]
} }
postForgotPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent postForgotPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent
postForgotPasswordR = registerHelper True forgotPasswordR postForgotPasswordR = registerHelper True forgotPasswordR
getVerifyR :: YesodAuthEmail site getVerifyR :: YesodAuthEmail site
=> AuthEmailId site => AuthEmailId site
-> Text -> Text
-> HandlerT Auth (HandlerT site IO) TypedContent -> AuthHandler site TypedContent
getVerifyR lid key = do getVerifyR lid key = do
realKey <- lift $ getVerifyKey lid realKey <- lift $ getVerifyKey lid
memail <- lift $ getEmail lid memail <- lift $ getEmail lid
@ -612,7 +612,7 @@ parseCreds = withObject "creds" (\obj -> do
return (email', pass)) return (email', pass))
postLoginR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent postLoginR :: YesodAuthEmail master => AuthHandler master TypedContent
postLoginR = do postLoginR = do
result <- lift $ runInputPostResult $ (,) result <- lift $ runInputPostResult $ (,)
<$> ireq textField "email" <$> ireq textField "email"
@ -658,7 +658,7 @@ postLoginR = do
then Msg.InvalidEmailPass then Msg.InvalidEmailPass
else Msg.InvalidUsernamePass else Msg.InvalidUsernamePass
getPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent getPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent
getPasswordR = do getPasswordR = do
maid <- lift maybeAuthId maid <- lift maybeAuthId
case maid of case maid of
@ -670,7 +670,7 @@ getPasswordR = do
-- | Default implementation of 'setPasswordHandler'. -- | Default implementation of 'setPasswordHandler'.
-- --
-- @since 1.2.6 -- @since 1.2.6
defaultSetPasswordHandler :: YesodAuthEmail master => Bool -> HandlerT Auth (HandlerT master IO) TypedContent defaultSetPasswordHandler :: YesodAuthEmail master => Bool -> AuthHandler master TypedContent
defaultSetPasswordHandler needOld = do defaultSetPasswordHandler needOld = do
messageRender <- lift getMessageRender messageRender <- lift getMessageRender
toParent <- getRouteToParent toParent <- getRouteToParent
@ -749,7 +749,7 @@ parsePassword = withObject "password" (\obj -> do
curr <- obj .:? "current" curr <- obj .:? "current"
return (email', pass, curr)) return (email', pass, curr))
postPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent postPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent
postPasswordR = do postPasswordR = do
maid <- lift maybeAuthId maid <- lift maybeAuthId
(creds :: Result Value) <- lift parseCheckJsonBody (creds :: Result Value) <- lift parseCheckJsonBody
@ -773,14 +773,14 @@ postPasswordR = do
mrealpass <- lift $ getPassword aid mrealpass <- lift $ getPassword aid
case (mrealpass, current) of case (mrealpass, current) of
(Nothing, _) -> (Nothing, _) ->
lift $ loginErrorMessage (tm setpassR) "You do not currently have a password set on your account" liftHandler $ loginErrorMessage (tm setpassR) "You do not currently have a password set on your account"
(_, Nothing) -> (_, Nothing) ->
loginErrorMessageI LoginR Msg.BadSetPass loginErrorMessageI LoginR Msg.BadSetPass
(Just realpass, Just current') -> do (Just realpass, Just current') -> do
passValid <- lift $ verifyPassword current' realpass passValid <- liftHandler $ verifyPassword current' realpass
if passValid if passValid
then confirmPassword aid tm jcreds then confirmPassword aid tm jcreds
else lift $ loginErrorMessage (tm setpassR) "Invalid current password, please try again" else liftHandler $ loginErrorMessage (tm setpassR) "Invalid current password, please try again"
where where
msgOk = Msg.PassUpdated msgOk = Msg.PassUpdated

View File

@ -41,6 +41,7 @@ library
, persistent >= 2.1 && < 2.8 , persistent >= 2.1 && < 2.8
, persistent-template >= 2.1 && < 2.8 , persistent-template >= 2.1 && < 2.8
, http-client , http-client
, http-client-tls
, http-conduit >= 2.1 , http-conduit >= 2.1
, aeson >= 0.7 , aeson >= 0.7
, lifted-base >= 0.1 , lifted-base >= 0.1
@ -61,6 +62,7 @@ library
, conduit , conduit
, conduit-extra , conduit-extra
, nonce >= 1.0.2 && < 1.1 , nonce >= 1.0.2 && < 1.1
, unliftio-core
if flag(network-uri) if flag(network-uri)
build-depends: network-uri >= 2.6 build-depends: network-uri >= 2.6

View File

@ -76,6 +76,7 @@ module Yesod.Core
, getApprootText , getApprootText
-- * Subsites -- * Subsites
, MonadSubHandler (..) , MonadSubHandler (..)
, SubsiteData
-- * Misc -- * Misc
, yesodVersion , yesodVersion
, yesodRender , yesodRender