diff --git a/yesod-auth/Yesod/Auth.hs b/yesod-auth/Yesod/Auth.hs index 503443b5..70b9648c 100644 --- a/yesod-auth/Yesod/Auth.hs +++ b/yesod-auth/Yesod/Auth.hs @@ -22,6 +22,7 @@ module Yesod.Auth -- * Plugin interface , Creds (..) , setCreds + , setCredsRedirect , clearCreds , loginErrorMessage , loginErrorMessageI @@ -36,6 +37,9 @@ module Yesod.Auth , AuthHandler -- * Internal , credsKey + , provideJsonMessage + , messageJson401 + , asHtml ) where import Control.Monad (when) @@ -64,6 +68,7 @@ import Control.Exception (Exception) import Network.HTTP.Types (unauthorized401) import Control.Monad.Trans.Resource (MonadResourceBase) import qualified Control.Monad.Trans.Writer as Writer +import Control.Monad (void) type AuthRoute = Route Auth @@ -74,7 +79,7 @@ type Piece = Text data AuthPlugin master = AuthPlugin { apName :: Text - , apDispatch :: Method -> [Piece] -> AuthHandler master () + , apDispatch :: Method -> [Piece] -> AuthHandler master TypedContent , apLogin :: (Route Auth -> Route master) -> WidgetT master IO () } @@ -106,7 +111,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage authPlugins :: master -> [AuthPlugin master] -- | What to show on the login page. - loginHandler :: AuthHandler master RepHtml + loginHandler :: AuthHandler master Html loginHandler = do tp <- getRouteToParent lift $ defaultLayout $ do @@ -171,9 +176,6 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage onErrorHtml dest msg = do setMessage $ toHtml msg fmap asHtml $ redirect dest - where - asHtml :: Html -> Html - asHtml = id -- | Internal session key used to hold the authentication information. -- @@ -227,7 +229,7 @@ cachedAuth aid = runMaybeT $ do loginErrorMessageI :: (MonadResourceBase m, YesodAuth master) => Route child -> AuthMessage - -> HandlerT child (HandlerT master m) a + -> HandlerT child (HandlerT master m) TypedContent loginErrorMessageI dest msg = do toParent <- getRouteToParent lift $ loginErrorMessageMasterI (toParent dest) msg @@ -236,7 +238,7 @@ loginErrorMessageI dest msg = do loginErrorMessageMasterI :: (YesodAuth master, MonadResourceBase m, RenderMessage master AuthMessage) => Route master -> AuthMessage - -> HandlerT master m a + -> HandlerT master m TypedContent loginErrorMessageMasterI dest msg = do mr <- getMessageRender loginErrorMessage dest (mr msg) @@ -246,47 +248,55 @@ loginErrorMessageMasterI dest msg = do loginErrorMessage :: (YesodAuth master, MonadResourceBase m) => Route master -> Text - -> HandlerT master m a -loginErrorMessage dest msg = - sendResponseStatus unauthorized401 =<< ( - selectRep $ do - provideRep $ do - onErrorHtml dest msg - provideJsonMessage msg - ) + -> HandlerT master m TypedContent +loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg) + +messageJson401 :: MonadResourceBase m => Text -> HandlerT master m Html -> HandlerT master m TypedContent +messageJson401 msg html = selectRep $ do + provideRep html + provideRep $ do + let obj = object ["message" .= msg] + void $ sendResponseStatus unauthorized401 obj + return obj provideJsonMessage :: Monad m => Text -> Writer.Writer (Endo [ProvidedRep m]) () provideJsonMessage msg = provideRep $ return $ object ["message" .= msg] +setCredsRedirect :: YesodAuth master + => Creds master -- ^ new credentials + -> HandlerT master IO TypedContent +setCredsRedirect creds = do + y <- getYesod + maid <- getAuthId creds + case maid of + Nothing -> + case authRoute y of + Nothing -> do + messageJson401 "Invalid Login" $ defaultLayout $ + toWidget [shamlet|

Invalid login|] + Just ar -> loginErrorMessageMasterI ar Msg.InvalidLogin + Just aid -> do + setSession credsKey $ toPathPiece aid + onLogin + res <- selectRep $ do + provideRepType typeHtml $ + fmap asHtml $ redirectUltDest $ loginDest y + provideJsonMessage "Login Successful" + sendResponse res + -- | Sets user credentials for the session after checking them with authentication backends. setCreds :: YesodAuth master => Bool -- ^ if HTTP redirects should be done -> Creds master -- ^ new credentials -> HandlerT master IO () -setCreds doRedirects creds = do - y <- getYesod - maid <- getAuthId creds - case maid of - Nothing -> when doRedirects $ do - case authRoute y of - Nothing -> do - sendResponseStatus unauthorized401 =<< ( - selectRep $ do - provideRep $ defaultLayout $ toWidget [shamlet|

Invalid login|] - provideJsonMessage "Invalid Login" - ) - Just ar -> loginErrorMessageMasterI ar Msg.InvalidLogin - Just aid -> do - setSession credsKey $ toPathPiece aid - when doRedirects $ do - onLogin - res <- selectRep $ do - provideRepType typeHtml $ do - _ <- redirectUltDest $ loginDest y - return () - provideJsonMessage "Login Successful" - sendResponse res +setCreds doRedirects creds = + if doRedirects + then void $ setCredsRedirect creds + else do maid <- getAuthId creds + case maid of + Nothing -> return () + Just aid -> setSession credsKey $ toPathPiece aid -- | Clears current user credentials for the session. -- @@ -327,7 +337,7 @@ setUltDestReferer' = lift $ do master <- getYesod when (redirectToReferer master) setUltDestReferer -getLoginR :: AuthHandler master RepHtml +getLoginR :: AuthHandler master Html getLoginR = setUltDestReferer' >> loginHandler getLogoutR :: AuthHandler master () @@ -336,7 +346,7 @@ getLogoutR = setUltDestReferer' >> redirectToPost LogoutR postLogoutR :: AuthHandler master () postLogoutR = lift $ clearCreds True -handlePluginR :: Text -> [Text] -> AuthHandler master () +handlePluginR :: Text -> [Text] -> AuthHandler master TypedContent handlePluginR plugin pieces = do master <- lift getYesod env <- waiRequest @@ -423,3 +433,6 @@ instance Exception AuthException instance YesodAuth master => YesodSubDispatch Auth (HandlerT master IO) where yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth) + +asHtml :: Html -> Html +asHtml = id diff --git a/yesod-auth/Yesod/Auth/BrowserId.hs b/yesod-auth/Yesod/Auth/BrowserId.hs index ee7617b9..3e2875c7 100644 --- a/yesod-auth/Yesod/Auth/BrowserId.hs +++ b/yesod-auth/Yesod/Auth/BrowserId.hs @@ -77,7 +77,7 @@ authBrowserId bis@BrowserIdSettings {..} = AuthPlugin $logErrorS "yesod-auth" "BrowserID assertion failure" tm <- getRouteToParent lift $ loginErrorMessage (tm LoginR) "BrowserID login error." - Just email -> lift $ setCreds True Creds + Just email -> lift $ setCredsRedirect Creds { credsPlugin = pid , credsIdent = email , credsExtra = [] diff --git a/yesod-auth/Yesod/Auth/Dummy.hs b/yesod-auth/Yesod/Auth/Dummy.hs index 9670f709..323f0d10 100644 --- a/yesod-auth/Yesod/Auth/Dummy.hs +++ b/yesod-auth/Yesod/Auth/Dummy.hs @@ -18,7 +18,7 @@ authDummy = where dispatch "POST" [] = do ident <- lift $ runInputPost $ ireq textField "ident" - lift $ setCreds True $ Creds "dummy" ident [] + lift $ setCredsRedirect $ Creds "dummy" ident [] dispatch _ _ = notFound url = PluginR "dummy" [] login authToMaster = diff --git a/yesod-auth/Yesod/Auth/Email.hs b/yesod-auth/Yesod/Auth/Email.hs index 9cdb3061..13296cc8 100644 --- a/yesod-auth/Yesod/Auth/Email.hs +++ b/yesod-auth/Yesod/Auth/Email.hs @@ -41,8 +41,8 @@ import qualified Crypto.PasswordStore as PS import qualified Text.Email.Validate import qualified Yesod.Auth.Message as Msg import Control.Applicative ((<$>), (<*>)) +import Control.Monad (void) import Yesod.Form -import Control.Monad (when) import Data.Time (getCurrentTime, addUTCTime) import Safe (readMay) @@ -78,7 +78,11 @@ data EmailCreds site = EmailCreds , emailCredsEmail :: Email } -class (YesodAuth site, PathPiece (AuthEmailId site)) => YesodAuthEmail site where +class ( YesodAuth site + , PathPiece (AuthEmailId site) + , (RenderMessage site Msg.AuthMessage) + ) + => YesodAuthEmail site where type AuthEmailId site -- | Add a new email address to the database, but indicate that the address @@ -167,10 +171,14 @@ class (YesodAuth site, PathPiece (AuthEmailId site)) => YesodAuthEmail site wher -- | Response after sending a confirmation email. -- -- Since 1.2.2 - confirmationEmailSentResponse :: Text -> HandlerT site IO Html - confirmationEmailSentResponse identifier = defaultLayout $ do - setTitleI Msg.ConfirmationEmailSentTitle - [whamlet|

_{Msg.ConfirmationEmailSent identifier}|] + confirmationEmailSentResponse :: Text -> HandlerT site IO TypedContent + confirmationEmailSentResponse identifier = do + mr <- getMessageRender + messageJson401 (mr msg) $ defaultLayout $ do + setTitleI Msg.ConfirmationEmailSentTitle + [whamlet|

_{msg}|] + where + msg = Msg.ConfirmationEmailSent identifier -- | Additional normalization of email addresses, besides standard canonicalization. -- @@ -183,6 +191,7 @@ class (YesodAuth site, PathPiece (AuthEmailId site)) => YesodAuthEmail site wher normalizeEmailAddress :: site -> Text -> Text normalizeEmailAddress _ = TS.toLower + authEmail :: YesodAuthEmail m => AuthPlugin m authEmail = AuthPlugin "email" dispatch $ \tm -> @@ -235,40 +244,46 @@ getRegisterR = do registerHelper :: YesodAuthEmail master => Bool -- ^ allow usernames? -> Route Auth - -> HandlerT Auth (HandlerT master IO) Html + -> HandlerT Auth (HandlerT master IO) TypedContent registerHelper allowUsername dest = do y <- lift getYesod midentifier <- lookupPostParam "email" - identifier <- - case midentifier of - Nothing -> loginErrorMessageI dest Msg.NoIdentifierProvided + let eidentifier = case midentifier of + Nothing -> Left Msg.NoIdentifierProvided Just x | Just x' <- Text.Email.Validate.canonicalizeEmail (encodeUtf8 x) -> - return $ normalizeEmailAddress y $ decodeUtf8With lenientDecode x' - | allowUsername -> return $ TS.strip x - | otherwise -> loginErrorMessageI dest Msg.InvalidEmailAddress + Right $ normalizeEmailAddress y $ decodeUtf8With lenientDecode x' + | allowUsername -> Right $ TS.strip x + | otherwise -> Left Msg.InvalidEmailAddress - mecreds <- lift $ getEmailCreds identifier - (lid, verKey, email) <- - case mecreds of - Just (EmailCreds lid _ _ (Just key) email) -> return (lid, key, email) - Just (EmailCreds lid _ _ Nothing email) -> do - key <- liftIO $ randomKey y - lift $ setVerifyKey lid key - return (lid, key, email) - Nothing - | allowUsername -> - loginErrorMessageI dest (Msg.IdentifierNotFound identifier) - | otherwise -> do - key <- liftIO $ randomKey y - lid <- lift $ addUnverified identifier key - return (lid, key, identifier) - render <- getUrlRender - let verUrl = render $ verify (toPathPiece lid) verKey - lift $ sendVerifyEmail email verKey verUrl - lift $ confirmationEmailSentResponse identifier + case eidentifier of + Left route -> loginErrorMessageI dest route + Right identifier -> do -postRegisterR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html + mecreds <- lift $ getEmailCreds identifier + registerCreds <- + case mecreds of + Just (EmailCreds lid _ _ (Just key) email) -> return $ Just (lid, key, email) + Just (EmailCreds lid _ _ Nothing email) -> do + key <- liftIO $ randomKey y + lift $ setVerifyKey lid key + return $ Just (lid, key, email) + Nothing + | allowUsername -> return Nothing + | otherwise -> do + key <- liftIO $ randomKey y + lid <- lift $ addUnverified identifier key + return $ Just (lid, key, identifier) + + case registerCreds of + Nothing -> loginErrorMessageI dest (Msg.IdentifierNotFound identifier) + Just (lid, verKey, email) -> do + render <- getUrlRender + let verUrl = render $ verify (toPathPiece lid) verKey + lift $ sendVerifyEmail email verKey verUrl + lift $ confirmationEmailSentResponse identifier + +postRegisterR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent postRegisterR = registerHelper False registerR getForgotPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html @@ -286,35 +301,43 @@ getForgotPasswordR = do