Add AuthenticationResult and authenticate function
- getAuthId returns Maybe AuthId with no useful information in the Nothing case. - AuthenticationResult includes whether it was a User or Server error (with an accompanying message) in the failure case. - User errors are displayed back to the user and have a 401 status in JSON responses. Server errors are logged and a generic error message is presented to the user, with a 500 status in JSON responses. Resolves #956
This commit is contained in:
parent
c7d41f2395
commit
3564e1f746
@ -28,6 +28,7 @@ module Yesod.Auth
|
|||||||
, loginErrorMessage
|
, loginErrorMessage
|
||||||
, loginErrorMessageI
|
, loginErrorMessageI
|
||||||
-- * User functions
|
-- * User functions
|
||||||
|
, AuthenticationResult (..)
|
||||||
, defaultMaybeAuthId
|
, defaultMaybeAuthId
|
||||||
, maybeAuthPair
|
, maybeAuthPair
|
||||||
, maybeAuth
|
, maybeAuth
|
||||||
@ -67,7 +68,7 @@ import qualified Yesod.Auth.Message as Msg
|
|||||||
import Yesod.Form (FormMessage)
|
import Yesod.Form (FormMessage)
|
||||||
import Data.Typeable (Typeable)
|
import Data.Typeable (Typeable)
|
||||||
import Control.Exception (Exception)
|
import Control.Exception (Exception)
|
||||||
import Network.HTTP.Types (unauthorized401)
|
import Network.HTTP.Types (Status, internalServerError500, unauthorized401)
|
||||||
import Control.Monad.Trans.Resource (MonadResourceBase)
|
import Control.Monad.Trans.Resource (MonadResourceBase)
|
||||||
import qualified Control.Monad.Trans.Writer as Writer
|
import qualified Control.Monad.Trans.Writer as Writer
|
||||||
import Control.Monad (void)
|
import Control.Monad (void)
|
||||||
@ -79,6 +80,12 @@ type AuthHandler master a = YesodAuth master => HandlerT Auth (HandlerT master I
|
|||||||
type Method = Text
|
type Method = Text
|
||||||
type Piece = Text
|
type Piece = Text
|
||||||
|
|
||||||
|
-- | The result of an authentication based on credentials
|
||||||
|
data AuthenticationResult master
|
||||||
|
= Authenticated (AuthId master) -- ^ Authenticated successfully
|
||||||
|
| UserError AuthMessage -- ^ Invalid credentials provided by user
|
||||||
|
| ServerError Text -- ^ Some other error
|
||||||
|
|
||||||
data AuthPlugin master = AuthPlugin
|
data AuthPlugin master = AuthPlugin
|
||||||
{ apName :: Text
|
{ apName :: Text
|
||||||
, apDispatch :: Method -> [Piece] -> AuthHandler master TypedContent
|
, apDispatch :: Method -> [Piece] -> AuthHandler master TypedContent
|
||||||
@ -110,8 +117,27 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
|
|||||||
-- destination exists.
|
-- destination exists.
|
||||||
logoutDest :: master -> Route master
|
logoutDest :: master -> Route master
|
||||||
|
|
||||||
|
-- | Perform authentication based on the given credentials.
|
||||||
|
--
|
||||||
|
-- Default implementation is in terms of @'getAuthId'@
|
||||||
|
--
|
||||||
|
authenticate :: Creds master -> HandlerT master IO (AuthenticationResult master)
|
||||||
|
authenticate creds = do
|
||||||
|
muid <- getAuthId creds
|
||||||
|
|
||||||
|
return $ maybe (UserError Msg.InvalidLogin) Authenticated muid
|
||||||
|
|
||||||
-- | Determine the ID associated with the set of credentials.
|
-- | Determine the ID associated with the set of credentials.
|
||||||
|
--
|
||||||
|
-- Default implementation is in terms of @'authenticate'@
|
||||||
|
--
|
||||||
getAuthId :: Creds master -> HandlerT master IO (Maybe (AuthId master))
|
getAuthId :: Creds master -> HandlerT master IO (Maybe (AuthId master))
|
||||||
|
getAuthId creds = do
|
||||||
|
auth <- authenticate creds
|
||||||
|
|
||||||
|
return $ case auth of
|
||||||
|
Authenticated auid -> Just auid
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
-- | Which authentication backends to use.
|
-- | Which authentication backends to use.
|
||||||
authPlugins :: master -> [AuthPlugin master]
|
authPlugins :: master -> [AuthPlugin master]
|
||||||
@ -175,6 +201,10 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
|
|||||||
setMessage $ toHtml msg
|
setMessage $ toHtml msg
|
||||||
fmap asHtml $ redirect dest
|
fmap asHtml $ redirect dest
|
||||||
|
|
||||||
|
{-# MINIMAL loginDest, logoutDest, (authenticate | getAuthId), authPlugins, authHttpManager #-}
|
||||||
|
|
||||||
|
{-# DEPRECATED getAuthId "Define 'authenticate' instead; 'getAuthId' will be removed in the next major version" #-}
|
||||||
|
|
||||||
-- | Internal session key used to hold the authentication information.
|
-- | Internal session key used to hold the authentication information.
|
||||||
--
|
--
|
||||||
-- Since 1.2.3
|
-- Since 1.2.3
|
||||||
@ -232,11 +262,21 @@ loginErrorMessage :: (YesodAuth master, MonadResourceBase m)
|
|||||||
loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg)
|
loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg)
|
||||||
|
|
||||||
messageJson401 :: MonadResourceBase m => Text -> HandlerT master m Html -> HandlerT master m TypedContent
|
messageJson401 :: MonadResourceBase m => Text -> HandlerT master m Html -> HandlerT master m TypedContent
|
||||||
messageJson401 msg html = selectRep $ do
|
messageJson401 = messageJsonStatus unauthorized401
|
||||||
|
|
||||||
|
messageJson500 :: MonadResourceBase m => Text -> HandlerT master m Html -> HandlerT master m TypedContent
|
||||||
|
messageJson500 = messageJsonStatus internalServerError500
|
||||||
|
|
||||||
|
messageJsonStatus :: MonadResourceBase m
|
||||||
|
=> Status
|
||||||
|
-> Text
|
||||||
|
-> HandlerT master m Html
|
||||||
|
-> HandlerT master m TypedContent
|
||||||
|
messageJsonStatus status msg html = selectRep $ do
|
||||||
provideRep html
|
provideRep html
|
||||||
provideRep $ do
|
provideRep $ do
|
||||||
let obj = object ["message" .= msg]
|
let obj = object ["message" .= msg]
|
||||||
void $ sendResponseStatus unauthorized401 obj
|
void $ sendResponseStatus status obj
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
provideJsonMessage :: Monad m => Text -> Writer.Writer (Endo [ProvidedRep m]) ()
|
provideJsonMessage :: Monad m => Text -> Writer.Writer (Endo [ProvidedRep m]) ()
|
||||||
@ -248,15 +288,9 @@ setCredsRedirect :: YesodAuth master
|
|||||||
-> HandlerT master IO TypedContent
|
-> HandlerT master IO TypedContent
|
||||||
setCredsRedirect creds = do
|
setCredsRedirect creds = do
|
||||||
y <- getYesod
|
y <- getYesod
|
||||||
maid <- getAuthId creds
|
auth <- authenticate creds
|
||||||
case maid of
|
case auth of
|
||||||
Nothing ->
|
Authenticated aid -> do
|
||||||
case authRoute y of
|
|
||||||
Nothing -> do
|
|
||||||
messageJson401 "Invalid Login" $ authLayout $
|
|
||||||
toWidget [shamlet|<h1>Invalid login|]
|
|
||||||
Just ar -> loginErrorMessageMasterI ar Msg.InvalidLogin
|
|
||||||
Just aid -> do
|
|
||||||
setSession credsKey $ toPathPiece aid
|
setSession credsKey $ toPathPiece aid
|
||||||
onLogin
|
onLogin
|
||||||
res <- selectRep $ do
|
res <- selectRep $ do
|
||||||
@ -265,6 +299,30 @@ setCredsRedirect creds = do
|
|||||||
provideJsonMessage "Login Successful"
|
provideJsonMessage "Login Successful"
|
||||||
sendResponse res
|
sendResponse res
|
||||||
|
|
||||||
|
UserError msg ->
|
||||||
|
case authRoute y of
|
||||||
|
Nothing -> do
|
||||||
|
msg' <- renderMessage' msg
|
||||||
|
messageJson401 msg' $ authLayout $ -- TODO
|
||||||
|
toWidget [whamlet|<h1>_{msg}|]
|
||||||
|
Just ar -> loginErrorMessageMasterI ar msg
|
||||||
|
|
||||||
|
ServerError msg -> do
|
||||||
|
$(logError) msg
|
||||||
|
|
||||||
|
case authRoute y of
|
||||||
|
Nothing -> do
|
||||||
|
msg' <- renderMessage' Msg.AuthError
|
||||||
|
messageJson500 msg' $ authLayout $
|
||||||
|
toWidget [whamlet|<h1>_{Msg.AuthError}|]
|
||||||
|
Just ar -> loginErrorMessageMasterI ar Msg.AuthError
|
||||||
|
|
||||||
|
where
|
||||||
|
renderMessage' msg = do
|
||||||
|
langs <- languages
|
||||||
|
master <- getYesod
|
||||||
|
return $ renderAuthMessage master langs msg
|
||||||
|
|
||||||
-- | Sets user credentials for the session after checking them with authentication backends.
|
-- | Sets user credentials for the session after checking them with authentication backends.
|
||||||
setCreds :: YesodAuth master
|
setCreds :: YesodAuth master
|
||||||
=> Bool -- ^ if HTTP redirects should be done
|
=> Bool -- ^ if HTTP redirects should be done
|
||||||
@ -273,10 +331,10 @@ setCreds :: YesodAuth master
|
|||||||
setCreds doRedirects creds =
|
setCreds doRedirects creds =
|
||||||
if doRedirects
|
if doRedirects
|
||||||
then void $ setCredsRedirect creds
|
then void $ setCredsRedirect creds
|
||||||
else do maid <- getAuthId creds
|
else do auth <- authenticate creds
|
||||||
case maid of
|
case auth of
|
||||||
Nothing -> return ()
|
Authenticated aid -> setSession credsKey $ toPathPiece aid
|
||||||
Just aid -> setSession credsKey $ toPathPiece aid
|
_ -> return ()
|
||||||
|
|
||||||
-- | same as defaultLayoutJson, but uses authLayout
|
-- | same as defaultLayoutJson, but uses authLayout
|
||||||
authLayoutJson :: (YesodAuth site, ToJSON j)
|
authLayoutJson :: (YesodAuth site, ToJSON j)
|
||||||
|
|||||||
@ -61,6 +61,7 @@ data AuthMessage =
|
|||||||
| PasswordResetPrompt
|
| PasswordResetPrompt
|
||||||
| InvalidUsernamePass
|
| InvalidUsernamePass
|
||||||
| Logout
|
| Logout
|
||||||
|
| AuthError
|
||||||
|
|
||||||
-- | Defaults to 'englishMessage'.
|
-- | Defaults to 'englishMessage'.
|
||||||
defaultMessage :: AuthMessage -> Text
|
defaultMessage :: AuthMessage -> Text
|
||||||
@ -108,6 +109,7 @@ englishMessage PasswordResetPrompt = "Enter your e-mail address or username belo
|
|||||||
englishMessage InvalidUsernamePass = "Invalid username/password combination"
|
englishMessage InvalidUsernamePass = "Invalid username/password combination"
|
||||||
englishMessage (IdentifierNotFound ident) = "Login not found: " `mappend` ident
|
englishMessage (IdentifierNotFound ident) = "Login not found: " `mappend` ident
|
||||||
englishMessage Logout = "Logout"
|
englishMessage Logout = "Logout"
|
||||||
|
englishMessage AuthError = "Authentication Error" -- FIXME by Google Translate
|
||||||
|
|
||||||
portugueseMessage :: AuthMessage -> Text
|
portugueseMessage :: AuthMessage -> Text
|
||||||
portugueseMessage NoOpenID = "Nenhum identificador OpenID encontrado"
|
portugueseMessage NoOpenID = "Nenhum identificador OpenID encontrado"
|
||||||
@ -152,6 +154,7 @@ portugueseMessage InvalidUsernamePass = "Nome de usuário ou senha inválidos"
|
|||||||
-- TODO
|
-- TODO
|
||||||
portugueseMessage i@(IdentifierNotFound _) = englishMessage i
|
portugueseMessage i@(IdentifierNotFound _) = englishMessage i
|
||||||
portugueseMessage Logout = "Sair" -- FIXME by Google Translate
|
portugueseMessage Logout = "Sair" -- FIXME by Google Translate
|
||||||
|
portugueseMessage AuthError = "Erro de autenticação" -- FIXME by Google Translate
|
||||||
|
|
||||||
spanishMessage :: AuthMessage -> Text
|
spanishMessage :: AuthMessage -> Text
|
||||||
spanishMessage NoOpenID = "No se encuentra el identificador OpenID"
|
spanishMessage NoOpenID = "No se encuentra el identificador OpenID"
|
||||||
@ -196,6 +199,7 @@ spanishMessage InvalidUsernamePass = "Combinación de nombre de usuario/contrase
|
|||||||
-- TODO
|
-- TODO
|
||||||
spanishMessage i@(IdentifierNotFound _) = englishMessage i
|
spanishMessage i@(IdentifierNotFound _) = englishMessage i
|
||||||
spanishMessage Logout = "Finalizar la sesión" -- FIXME by Google Translate
|
spanishMessage Logout = "Finalizar la sesión" -- FIXME by Google Translate
|
||||||
|
spanishMessage AuthError = "Error de autenticación" -- FIXME by Google Translate
|
||||||
|
|
||||||
swedishMessage :: AuthMessage -> Text
|
swedishMessage :: AuthMessage -> Text
|
||||||
swedishMessage NoOpenID = "Fann ej OpenID identifierare"
|
swedishMessage NoOpenID = "Fann ej OpenID identifierare"
|
||||||
@ -241,6 +245,7 @@ swedishMessage InvalidUsernamePass = "Ogiltig kombination av användarnamn och l
|
|||||||
-- TODO
|
-- TODO
|
||||||
swedishMessage i@(IdentifierNotFound _) = englishMessage i
|
swedishMessage i@(IdentifierNotFound _) = englishMessage i
|
||||||
swedishMessage Logout = "Loggar ut" -- FIXME by Google Translate
|
swedishMessage Logout = "Loggar ut" -- FIXME by Google Translate
|
||||||
|
swedishMessage AuthError = "Autentisering Fel" -- FIXME by Google Translate
|
||||||
|
|
||||||
germanMessage :: AuthMessage -> Text
|
germanMessage :: AuthMessage -> Text
|
||||||
germanMessage NoOpenID = "Kein OpenID-Identifier gefunden"
|
germanMessage NoOpenID = "Kein OpenID-Identifier gefunden"
|
||||||
@ -285,6 +290,7 @@ germanMessage InvalidUsernamePass = "Ungültige Kombination aus Nutzername und P
|
|||||||
-- TODO
|
-- TODO
|
||||||
germanMessage i@(IdentifierNotFound _) = englishMessage i
|
germanMessage i@(IdentifierNotFound _) = englishMessage i
|
||||||
germanMessage Logout = "Ausloggen" -- FIXME by Google Translate
|
germanMessage Logout = "Ausloggen" -- FIXME by Google Translate
|
||||||
|
germanMessage AuthError = "Authorisierungsfehler" -- FIXME by Google Translate
|
||||||
|
|
||||||
frenchMessage :: AuthMessage -> Text
|
frenchMessage :: AuthMessage -> Text
|
||||||
frenchMessage NoOpenID = "Aucun fournisseur OpenID n'a été trouvé"
|
frenchMessage NoOpenID = "Aucun fournisseur OpenID n'a été trouvé"
|
||||||
@ -328,6 +334,7 @@ frenchMessage PasswordResetPrompt = "Entrez votre courriel ou votre nom d'utilis
|
|||||||
frenchMessage InvalidUsernamePass = "La combinaison de ce mot de passe et de ce nom d'utilisateur n'existe pas."
|
frenchMessage InvalidUsernamePass = "La combinaison de ce mot de passe et de ce nom d'utilisateur n'existe pas."
|
||||||
frenchMessage (IdentifierNotFound ident) = "Nom d'utilisateur introuvable: " `mappend` ident
|
frenchMessage (IdentifierNotFound ident) = "Nom d'utilisateur introuvable: " `mappend` ident
|
||||||
frenchMessage Logout = "Déconnexion"
|
frenchMessage Logout = "Déconnexion"
|
||||||
|
frenchMessage AuthError = "Erreur d'authentification" -- FIXME by Google Translate
|
||||||
|
|
||||||
norwegianBokmålMessage :: AuthMessage -> Text
|
norwegianBokmålMessage :: AuthMessage -> Text
|
||||||
norwegianBokmålMessage NoOpenID = "Ingen OpenID-identifiserer funnet"
|
norwegianBokmålMessage NoOpenID = "Ingen OpenID-identifiserer funnet"
|
||||||
@ -372,6 +379,7 @@ norwegianBokmålMessage InvalidUsernamePass = "Invalid username/password combina
|
|||||||
-- TODO
|
-- TODO
|
||||||
norwegianBokmålMessage i@(IdentifierNotFound _) = englishMessage i
|
norwegianBokmålMessage i@(IdentifierNotFound _) = englishMessage i
|
||||||
norwegianBokmålMessage Logout = "Logge ut" -- FIXME by Google Translate
|
norwegianBokmålMessage Logout = "Logge ut" -- FIXME by Google Translate
|
||||||
|
norwegianBokmålMessage AuthError = "Godkjenningsfeil" -- FIXME by Google Translate
|
||||||
|
|
||||||
japaneseMessage :: AuthMessage -> Text
|
japaneseMessage :: AuthMessage -> Text
|
||||||
japaneseMessage NoOpenID = "OpenID識別子がありません"
|
japaneseMessage NoOpenID = "OpenID識別子がありません"
|
||||||
@ -416,6 +424,7 @@ japaneseMessage InvalidUsernamePass = "ユーザ名とパスワードの組み
|
|||||||
japaneseMessage (IdentifierNotFound ident) =
|
japaneseMessage (IdentifierNotFound ident) =
|
||||||
ident `mappend` "は登録されていません"
|
ident `mappend` "は登録されていません"
|
||||||
japaneseMessage Logout = "ログアウト" -- FIXME by Google Translate
|
japaneseMessage Logout = "ログアウト" -- FIXME by Google Translate
|
||||||
|
japaneseMessage AuthError = "認証エラー" -- FIXME by Google Translate
|
||||||
|
|
||||||
finnishMessage :: AuthMessage -> Text
|
finnishMessage :: AuthMessage -> Text
|
||||||
finnishMessage NoOpenID = "OpenID-tunnistetta ei löydy"
|
finnishMessage NoOpenID = "OpenID-tunnistetta ei löydy"
|
||||||
@ -461,6 +470,7 @@ finnishMessage InvalidUsernamePass = "Virheellinen käyttäjänimi tai salasana.
|
|||||||
-- TODO
|
-- TODO
|
||||||
finnishMessage i@(IdentifierNotFound _) = englishMessage i
|
finnishMessage i@(IdentifierNotFound _) = englishMessage i
|
||||||
finnishMessage Logout = "Kirjaudu ulos" -- FIXME by Google Translate
|
finnishMessage Logout = "Kirjaudu ulos" -- FIXME by Google Translate
|
||||||
|
finnishMessage AuthError = "Authentication Error" -- FIXME by Google Translate
|
||||||
|
|
||||||
chineseMessage :: AuthMessage -> Text
|
chineseMessage :: AuthMessage -> Text
|
||||||
chineseMessage NoOpenID = "无效的OpenID"
|
chineseMessage NoOpenID = "无效的OpenID"
|
||||||
@ -505,6 +515,7 @@ chineseMessage InvalidUsernamePass = "无效的用户名/密码组合"
|
|||||||
-- TODO
|
-- TODO
|
||||||
chineseMessage i@(IdentifierNotFound _) = englishMessage i
|
chineseMessage i@(IdentifierNotFound _) = englishMessage i
|
||||||
chineseMessage Logout = "註銷" -- FIXME by Google Translate
|
chineseMessage Logout = "註銷" -- FIXME by Google Translate
|
||||||
|
chineseMessage AuthError = "验证错误" -- FIXME by Google Translate
|
||||||
|
|
||||||
czechMessage :: AuthMessage -> Text
|
czechMessage :: AuthMessage -> Text
|
||||||
czechMessage NoOpenID = "Nebyl nalezen identifikátor OpenID"
|
czechMessage NoOpenID = "Nebyl nalezen identifikátor OpenID"
|
||||||
@ -547,6 +558,7 @@ czechMessage InvalidUsernamePass = "Neplatná kombinace uživatelského jména a
|
|||||||
-- TODO
|
-- TODO
|
||||||
czechMessage i@(IdentifierNotFound _) = englishMessage i
|
czechMessage i@(IdentifierNotFound _) = englishMessage i
|
||||||
czechMessage Logout = "Odhlásit" -- FIXME by Google Translate
|
czechMessage Logout = "Odhlásit" -- FIXME by Google Translate
|
||||||
|
czechMessage AuthError = "Chyba ověřování" -- FIXME by Google Translate
|
||||||
|
|
||||||
-- Так как e-mail – это фактическое сокращение словосочетания electronic mail,
|
-- Так как e-mail – это фактическое сокращение словосочетания electronic mail,
|
||||||
-- для русского перевода так же использовано сокращение: эл.почта
|
-- для русского перевода так же использовано сокращение: эл.почта
|
||||||
@ -592,6 +604,7 @@ russianMessage PasswordResetPrompt = "Введите адрес эл.почты
|
|||||||
russianMessage InvalidUsernamePass = "Неверное сочетание имени пользователя и пароля"
|
russianMessage InvalidUsernamePass = "Неверное сочетание имени пользователя и пароля"
|
||||||
russianMessage (IdentifierNotFound ident) = "Логин не найден: " `mappend` ident
|
russianMessage (IdentifierNotFound ident) = "Логин не найден: " `mappend` ident
|
||||||
russianMessage Logout = "Выйти"
|
russianMessage Logout = "Выйти"
|
||||||
|
russianMessage AuthError = "Ошибка аутентификации"
|
||||||
|
|
||||||
dutchMessage :: AuthMessage -> Text
|
dutchMessage :: AuthMessage -> Text
|
||||||
dutchMessage NoOpenID = "Geen OpenID identificator gevonden"
|
dutchMessage NoOpenID = "Geen OpenID identificator gevonden"
|
||||||
@ -635,3 +648,4 @@ dutchMessage PasswordResetPrompt = "Voer uw e-mailadres of gebruikersnaam hieron
|
|||||||
dutchMessage InvalidUsernamePass = "Ongeldige gebruikersnaam/wachtwoord combinatie"
|
dutchMessage InvalidUsernamePass = "Ongeldige gebruikersnaam/wachtwoord combinatie"
|
||||||
dutchMessage (IdentifierNotFound ident) = "Inloggegevens niet gevonden: " `mappend` ident
|
dutchMessage (IdentifierNotFound ident) = "Inloggegevens niet gevonden: " `mappend` ident
|
||||||
dutchMessage Logout = "Logout" -- FIXME NOT TRANSLATED
|
dutchMessage Logout = "Logout" -- FIXME NOT TRANSLATED
|
||||||
|
dutchMessage AuthError = "Verificatiefout" -- FIXME by Google Translate
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user