needOldPassword and checkPasswordSecurity
This commit is contained in:
parent
aa5781d4e4
commit
e99302e93d
@ -21,6 +21,9 @@ module Yesod.Auth.Email
|
|||||||
, SaltedPass
|
, SaltedPass
|
||||||
, VerStatus
|
, VerStatus
|
||||||
, Identifier
|
, Identifier
|
||||||
|
-- * Misc
|
||||||
|
, loginLinkKey
|
||||||
|
, setLoginLinkKey
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Network.Mail.Mime (randomString)
|
import Network.Mail.Mime (randomString)
|
||||||
@ -40,6 +43,8 @@ import qualified Yesod.Auth.Message as Msg
|
|||||||
import Control.Applicative ((<$>), (<*>))
|
import Control.Applicative ((<$>), (<*>))
|
||||||
import Yesod.Form
|
import Yesod.Form
|
||||||
import Control.Monad (when)
|
import Control.Monad (when)
|
||||||
|
import Data.Time (getCurrentTime, addUTCTime)
|
||||||
|
import Safe (readMay)
|
||||||
|
|
||||||
loginR, registerR, forgotPasswordR, setpassR :: AuthRoute
|
loginR, registerR, forgotPasswordR, setpassR :: AuthRoute
|
||||||
loginR = PluginR "email" ["login"]
|
loginR = PluginR "email" ["login"]
|
||||||
@ -136,6 +141,29 @@ class (YesodAuth site, PathPiece (AuthEmailId site)) => YesodAuthEmail site wher
|
|||||||
-- Since 1.2.0
|
-- Since 1.2.0
|
||||||
afterPasswordRoute :: site -> Route site
|
afterPasswordRoute :: site -> Route site
|
||||||
|
|
||||||
|
-- | Does the user need to provide the current password in order to set a
|
||||||
|
-- new password?
|
||||||
|
--
|
||||||
|
-- Default: if the user logged in via an email link do not require a password.
|
||||||
|
--
|
||||||
|
-- Since 1.2.1
|
||||||
|
needOldPassword :: AuthId site -> HandlerT site IO Bool
|
||||||
|
needOldPassword aid' = do
|
||||||
|
mkey <- lookupSession loginLinkKey
|
||||||
|
case mkey >>= readMay . TS.unpack of
|
||||||
|
Just (aidT, time) | Just aid <- fromPathPiece aidT, toPathPiece (aid `asTypeOf` aid') == toPathPiece aid' -> do
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
return $ addUTCTime (60 * 30) time <= now
|
||||||
|
_ -> return True
|
||||||
|
|
||||||
|
-- | Check that the given plain-text password meets minimum security standards.
|
||||||
|
--
|
||||||
|
-- Default: password is at least three characters.
|
||||||
|
checkPasswordSecurity :: AuthId site -> Text -> HandlerT site IO (Either Text ())
|
||||||
|
checkPasswordSecurity _ x
|
||||||
|
| TS.length x >= 3 = return $ Right ()
|
||||||
|
| otherwise = return $ Left "Password must be at least three characters"
|
||||||
|
|
||||||
authEmail :: YesodAuthEmail m => AuthPlugin m
|
authEmail :: YesodAuthEmail m => AuthPlugin m
|
||||||
authEmail =
|
authEmail =
|
||||||
AuthPlugin "email" dispatch $ \tm ->
|
AuthPlugin "email" dispatch $ \tm ->
|
||||||
@ -246,8 +274,10 @@ getForgotPasswordR = do
|
|||||||
postForgotPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html
|
postForgotPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html
|
||||||
postForgotPasswordR = registerHelper True forgotPasswordR
|
postForgotPasswordR = registerHelper True forgotPasswordR
|
||||||
|
|
||||||
getVerifyR :: YesodAuthEmail m
|
getVerifyR :: YesodAuthEmail site
|
||||||
=> AuthEmailId m -> Text -> HandlerT Auth (HandlerT m IO) Html
|
=> AuthEmailId site
|
||||||
|
-> Text
|
||||||
|
-> HandlerT Auth (HandlerT site IO) Html
|
||||||
getVerifyR lid key = do
|
getVerifyR lid key = do
|
||||||
realKey <- lift $ getVerifyKey lid
|
realKey <- lift $ getVerifyKey lid
|
||||||
memail <- lift $ getEmail lid
|
memail <- lift $ getEmail lid
|
||||||
@ -256,9 +286,10 @@ getVerifyR lid key = do
|
|||||||
muid <- lift $ verifyAccount lid
|
muid <- lift $ verifyAccount lid
|
||||||
case muid of
|
case muid of
|
||||||
Nothing -> return ()
|
Nothing -> return ()
|
||||||
Just _uid -> do
|
Just uid -> do
|
||||||
lift $ setCreds False $ Creds "email-verify" email [("verifiedEmail", email)] -- FIXME uid?
|
lift $ setCreds False $ Creds "email-verify" email [("verifiedEmail", email)] -- FIXME uid?
|
||||||
lift $ setMessageI Msg.AddressVerified
|
lift $ setMessageI Msg.AddressVerified
|
||||||
|
lift $ setLoginLinkKey uid
|
||||||
redirect setpassR
|
redirect setpassR
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
lift $ defaultLayout $ do
|
lift $ defaultLayout $ do
|
||||||
@ -304,12 +335,14 @@ postLoginR = do
|
|||||||
getPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html
|
getPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html
|
||||||
getPasswordR = do
|
getPasswordR = do
|
||||||
maid <- lift maybeAuthId
|
maid <- lift maybeAuthId
|
||||||
|
pass0 <- newIdent
|
||||||
pass1 <- newIdent
|
pass1 <- newIdent
|
||||||
pass2 <- newIdent
|
pass2 <- newIdent
|
||||||
case maid of
|
case maid of
|
||||||
Just _ -> return ()
|
Just _ -> return ()
|
||||||
Nothing -> loginErrorMessageI LoginR Msg.BadSetPass
|
Nothing -> loginErrorMessageI LoginR Msg.BadSetPass
|
||||||
tp <- getRouteToParent
|
tp <- getRouteToParent
|
||||||
|
needOld <- maybe (return True) (lift . needOldPassword) maid
|
||||||
lift $ defaultLayout $ do
|
lift $ defaultLayout $ do
|
||||||
setTitleI Msg.SetPassTitle
|
setTitleI Msg.SetPassTitle
|
||||||
[whamlet|
|
[whamlet|
|
||||||
@ -317,11 +350,17 @@ $newline never
|
|||||||
<h3>_{Msg.SetPass}
|
<h3>_{Msg.SetPass}
|
||||||
<form method="post" action="@{tp setpassR}">
|
<form method="post" action="@{tp setpassR}">
|
||||||
<table>
|
<table>
|
||||||
|
$if needOld
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<label for=#{pass0}>Current Password
|
||||||
|
<td>
|
||||||
|
<input ##{pass0} type="password" name="current" autofocus>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
<label for=#{pass1}>_{Msg.NewPass}
|
<label for=#{pass1}>_{Msg.NewPass}
|
||||||
<td>
|
<td>
|
||||||
<input ##{pass1} type="password" name="new" autofocus>
|
<input ##{pass1} type="password" name="new" :not needOld:autofocus>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
<label for=#{pass2}>_{Msg.ConfirmPass}
|
<label for=#{pass2}>_{Msg.ConfirmPass}
|
||||||
@ -334,20 +373,38 @@ $newline never
|
|||||||
|
|
||||||
postPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) ()
|
postPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) ()
|
||||||
postPasswordR = do
|
postPasswordR = do
|
||||||
|
maid <- lift maybeAuthId
|
||||||
|
aid <- case maid of
|
||||||
|
Nothing -> loginErrorMessageI LoginR Msg.BadSetPass
|
||||||
|
Just aid -> return aid
|
||||||
|
|
||||||
|
needOld <- lift $ needOldPassword aid
|
||||||
|
when needOld $ do
|
||||||
|
current <- lift $ runInputPost $ ireq textField "current"
|
||||||
|
mrealpass <- lift $ getPassword aid
|
||||||
|
case mrealpass of
|
||||||
|
Nothing -> loginErrorMessage setpassR "You do not currently have a password set on your account"
|
||||||
|
Just realpass
|
||||||
|
| isValidPass current realpass -> return ()
|
||||||
|
| otherwise -> loginErrorMessage setpassR "Invalid current password, please try again"
|
||||||
|
|
||||||
(new, confirm) <- lift $ runInputPost $ (,)
|
(new, confirm) <- lift $ runInputPost $ (,)
|
||||||
<$> ireq textField "new"
|
<$> ireq textField "new"
|
||||||
<*> ireq textField "confirm"
|
<*> ireq textField "confirm"
|
||||||
when (new /= confirm) $
|
when (new /= confirm) $
|
||||||
loginErrorMessageI setpassR Msg.PassMismatch
|
loginErrorMessageI setpassR Msg.PassMismatch
|
||||||
maid <- lift maybeAuthId
|
|
||||||
aid <- case maid of
|
isSecure <- lift $ checkPasswordSecurity aid new
|
||||||
Nothing -> loginErrorMessageI LoginR Msg.BadSetPass
|
case isSecure of
|
||||||
Just aid -> return aid
|
Left e -> loginErrorMessage setpassR e
|
||||||
|
Right () -> return ()
|
||||||
|
|
||||||
salted <- liftIO $ saltPass new
|
salted <- liftIO $ saltPass new
|
||||||
lift $ do
|
lift $ do
|
||||||
y <- getYesod
|
y <- getYesod
|
||||||
setPassword aid salted
|
setPassword aid salted
|
||||||
setMessageI Msg.PassUpdated
|
setMessageI Msg.PassUpdated
|
||||||
|
deleteSession loginLinkKey
|
||||||
redirect $ afterPasswordRoute y
|
redirect $ afterPasswordRoute y
|
||||||
|
|
||||||
saltLength :: Int
|
saltLength :: Int
|
||||||
@ -377,3 +434,18 @@ isValidPass' clear' salted' =
|
|||||||
where
|
where
|
||||||
clear = TS.unpack clear'
|
clear = TS.unpack clear'
|
||||||
salted = TS.unpack salted'
|
salted = TS.unpack salted'
|
||||||
|
|
||||||
|
-- | Session variable set when user logged in via a login link. See
|
||||||
|
-- 'needOldPassword'.
|
||||||
|
--
|
||||||
|
-- Since 1.2.1
|
||||||
|
loginLinkKey :: Text
|
||||||
|
loginLinkKey = "_AUTH_EMAIL_LOGIN_LINK"
|
||||||
|
|
||||||
|
-- | Set 'loginLinkKey' to the current time.
|
||||||
|
--
|
||||||
|
-- Since 1.2.1
|
||||||
|
setLoginLinkKey :: (YesodAuthEmail site, MonadHandler m, HandlerSite m ~ site) => AuthId site -> m ()
|
||||||
|
setLoginLinkKey aid = do
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
setSession loginLinkKey $ TS.pack $ show (toPathPiece aid, now)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-auth
|
name: yesod-auth
|
||||||
version: 1.2.0.2
|
version: 1.2.1
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman, Patrick Brisbin
|
author: Michael Snoyman, Patrick Brisbin
|
||||||
@ -49,6 +49,8 @@ library
|
|||||||
, email-validate >= 1.0
|
, email-validate >= 1.0
|
||||||
, data-default
|
, data-default
|
||||||
, resourcet
|
, resourcet
|
||||||
|
, safe
|
||||||
|
, time
|
||||||
|
|
||||||
exposed-modules: Yesod.Auth
|
exposed-modules: Yesod.Auth
|
||||||
Yesod.Auth.BrowserId
|
Yesod.Auth.BrowserId
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user