packages feed

yesod-auth 1.1.7 → 1.6.12.1

raw patch · 16 files changed

Files

+ ChangeLog.md view
@@ -0,0 +1,240 @@+# ChangeLog for yesod-auth++## 1.6.12.1++* Support `yesod-core` 1.7++## 1.6.12.0++* Add `afterPasswordRouteHandler` [#1863](https://github.com/yesodweb/yesod/pull/1863)+* Use crypton instead of cryptonite [#1838](https://github.com/yesodweb/yesod/pull/1838)+* Set `base >= 4.11` for less CPP and imports [#1876](https://github.com/yesodweb/yesod/pull/1876)+++## 1.6.11.3++* Add Romanian translation [#1809](https://github.com/yesodweb/yesod/pull/1809)++## 1.6.11.2++* Add support for aeson 2.2 [#1820](https://github.com/yesodweb/yesod/pull/1820)++## 1.6.11.1++* No star is type [#1797](https://github.com/yesodweb/yesod/pull/1797)++## 1.6.11++* Add support for aeson 2++## 1.6.10.5++* Fix German translations of AuthMessage [#1741](https://github.com/yesodweb/yesod/pull/1741)++## 1.6.10.4++* Add support for GHC 9 [#1737](https://github.com/yesodweb/yesod/pull/1737)++## 1.6.10.3++* Relax bounds for yesod-form 1.7++## 1.6.10.2++* Relax bounds for persistent 2.12++## 1.6.10.1++* Add support for Persistent 2.11 [#1701](https://github.com/yesodweb/yesod/pull/1701)++## 1.6.10++* Updated `AuthMessage` data type in `Yesod.Auth.Message` to accommodate registration flow where password is supplied initially: deprecated `AddressVerified` and split into `EmailVerifiedChangePass` and `EmailVerified`+* Fixed a bug in `getVerifyR` related to the above, where the incorrect message was displayed when password was set during registration+* Added `sendForgotPasswordEmail` to `YesodAuthEmail` typeclass, allowing for different emails for account registration vs. forgot password+* See pull request [#1662](https://github.com/yesodweb/yesod/pull/1662)++## 1.6.9++* Added `registerHelper` and `passwordResetHelper` methods to the `YesodAuthEmail` class, allowing for customizing behavior for user registration and forgot password requests [#1660](https://github.com/yesodweb/yesod/pull/1660)+* Exposed `defaultRegisterHelper` as default implementation for the above methods++## 1.6.8.1++* Email: Fix typo in `defaultEmailLoginHandler` template [#1605](https://github.com/yesodweb/yesod/pull/1605)+* Remove unnecessary deriving of Typeable++## 1.6.8++* Dummy: Add support for JSON submissions [#1619](https://github.com/yesodweb/yesod/pull/1619)++## 1.6.7++* Redirect behavior of `clearCreds` depends on request type [#1598](https://github.com/yesodweb/yesod/pull/1598)++## 1.6.6++* Deprecated `Yesod.Auth.GoogleEmail2`, see [#1579](https://github.com/yesodweb/yesod/issues/1579) and [migration blog post](https://pbrisbin.com/posts/googleemail2_deprecation/)++## 1.6.5++* Add support for persistent 2.9 [#1516](https://github.com/yesodweb/yesod/pull/1516), [#1561](https://github.com/yesodweb/yesod/pull/1561)++## 1.6.4.1++* Email: Fix forgot-password endpoint [#1537](https://github.com/yesodweb/yesod/pull/1537)++## 1.6.4++* Make `registerHelper` configurable [#1524](https://github.com/yesodweb/yesod/issues/1524)+* Email: Immediately register with a password [#1389](https://github.com/yesodweb/yesod/issues/1389)+To configure this new functionality:+  1. Define `addUnverifiedWithPass`, e.g:+  ```+  addUnverified email verkey = liftHandler $ runDB $ do+    void $ insert $ UserLogin email Nothing (Just verkey) False+    return email++  addUnverifiedWithPass email verkey pass = liftHandler $ runDB $ do+    void $ insert $ UserLogin email (Just pass) (Just verkey) False+    return email+  ```+  2. Add a `password` field to your client forms.++## 1.6.3++* Generalize GoogleEmail2.getPerson [#1501](https://github.com/yesodweb/yesod/pull/1501)++## 1.6.2++* Remove MINIMAL praggma for authHttpManager [#1489](https://github.com/yesodweb/yesod/issues/1489)++## 1.6.1++* Relax a number of type signatures [#1488](https://github.com/yesodweb/yesod/issues/1488)++## 1.6.0++* Upgrade to yesod-core 1.6.0++## 1.4.21++* Add redirectToCurrent to Yesod.Auth module for controlling setUltDestCurrent in redirectLogin [#1461](https://github.com/yesodweb/yesod/pull/1461)++## 1.4.20++* Extend `YesodAuthEmail` to support extensible password hashing via+  `hashAndSaltPassword` and `verifyPassword` functions++## 1.4.19++* Adjust English localization to distinguish between "log in" (verb) and "login" (noun)++## 1.4.18++* Expose Yesod.Auth.Util.PasswordStore++## 1.4.17.3++* Some translation fixes++## 1.4.17.2++* Move to cryptonite from cryptohash++## 1.4.17.1++* Some translation fixes++## 1.4.17++* Add Show instance for user credentials `Creds`+* Export pid type for identifying plugin+* Fix warnings+* Allow for a custom Email Login DOM with `emailLoginHandler`++## 1.4.16++* Fix email provider [#1330](https://github.com/yesodweb/yesod/issues/1330)+* Document JSON endpoints of Yesod.Auth.Email++## 1.4.15++* Add JSON endpoints to Yesod.Auth.Email module+* Export croatianMessage from Message module+* Minor Haddock rendering fixes at Auth.Email module++## 1.4.14++* Remove Google OpenID link [#1309](https://github.com/yesodweb/yesod/pull/1309)+* Add CSRF Security check in `registerHelperFunction` [#1302](https://github.com/yesodweb/yesod/pull/1302)++## 1.4.13.5++* Translation fix++## 1.4.13.4++* Improved translations+* peristent 2.6++## 1.4.13.3++* Doc update (and a warning)++## 1.4.13.1++* Add CSRF token to login form from `Yesod.Auth.Dummy` [#1205](https://github.com/yesodweb/yesod/pull/1205)++## 1.4.13++* Add a CSRF token to the login form from `Yesod.Auth.Hardcoded`, making it compatible with the CSRF middleware [#1161](https://github.com/yesodweb/yesod/pull/1161)+* Multiple session messages. [#1187](https://github.com/yesodweb/yesod/pull/1187)++## 1.4.12++* Deprecated Yesod.Auth.GoogleEmail++## 1.4.11++Add Yesod.Auth.Hardcoded++## 1.4.9++* Expose defaultLoginHandler++## 1.4.8++* GoogleEmail2: proper error message when permission denied++## 1.4.7++* add a runHttpRequest function for handling HTTP errors++## 1.4.6++* Use nonce package to generate verification keys and CSRF tokens [#1011](https://github.com/yesodweb/yesod/pull/1011)++## 1.4.5++* Adds export of email verify route [#980](https://github.com/yesodweb/yesod/pull/980)++## 1.4.4++* Add AuthenticationResult and authenticate function [#959](https://github.com/yesodweb/yesod/pull/959)++## 1.4.3++* Added means to fetch user's Google profile [#936](https://github.com/yesodweb/yesod/pull/936)++## 1.4.2++* Perform `onLogout` before session cleaning [#923](https://github.com/yesodweb/yesod/pull/923)++## 1.4.1.3++[Updated french translation of Yesod.Auth.Message. #904](https://github.com/yesodweb/yesod/pull/904)++## 1.4.1++Dutch translation added.
+ README.md view
@@ -0,0 +1,12 @@+## yesod-auth++This package provides a pluggable mechanism for allowing users to authenticate+with your site. It comes with a number of common plugins, such as OpenID,+BrowserID (a.k.a., Mozilla Persona), and email. Other packages are available+from Hackage as well. If you've written such an add-on, please notify me so+that it can be added to this description.++* [yesod-auth-oauth2](https://hackage.haskell.org/package/yesod-auth-oauth2): Library to authenticate with OAuth 2.0.+* [yesod-auth-account](http://hackage.haskell.org/package/yesod-auth-account): An account authentication plugin for Yesod+* [yesod-auth-hashdb](http://www.stackage.org/package/yesod-auth-hashdb): The HashDB module previously packaged in yesod-auth, now with stronger, but compatible, security.+* [yesod-auth-bcrypt](https://hackage.haskell.org/package/yesod-auth-bcrypt): An alternative to the HashDB module.
Yesod/Auth.hs view
@@ -1,12 +1,18 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RankNTypes #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-} {-# OPTIONS_GHC -fno-warn-orphans #-}+ module Yesod.Auth     ( -- * Subsite       Auth@@ -15,55 +21,82 @@     , AuthPlugin (..)     , getAuth     , YesodAuth (..)+    , YesodAuthPersist (..)       -- * Plugin interface     , Creds (..)     , setCreds+    , setCredsRedirect     , clearCreds+    , loginErrorMessage+    , loginErrorMessageI       -- * User functions+    , AuthenticationResult (..)     , defaultMaybeAuthId+    , defaultLoginHandler+    , maybeAuthPair     , maybeAuth     , requireAuthId+    , requireAuthPair     , requireAuth       -- * Exception     , AuthException (..)+      -- * Helper+    , MonadAuthHandler+    , AuthHandler+      -- * Internal+    , credsKey+    , provideJsonMessage+    , messageJson401+    , asHtml     ) where -import Control.Monad                 (when)  +import Control.Monad                 (when) import Control.Monad.Trans.Maybe -import Data.Aeson+import Yesod.Auth.Routes import Data.Text.Encoding (decodeUtf8With) import Data.Text.Encoding.Error (lenientDecode) import           Data.Text (Text) import qualified Data.Text as T import qualified Data.HashMap.Lazy as Map-import Network.HTTP.Conduit (Manager)--import Language.Haskell.TH.Syntax hiding (lift)+import Data.Monoid (Endo)+import Network.HTTP.Client (Manager, Request, withResponse, Response, BodyReader)+import Network.HTTP.Client.TLS (getGlobalManager)  import qualified Network.Wai as W-import Text.Hamlet (shamlet)  import Yesod.Core import Yesod.Persist-import Yesod.Json import Yesod.Auth.Message (AuthMessage, defaultMessage) import qualified Yesod.Auth.Message as Msg import Yesod.Form (FormMessage) import Data.Typeable (Typeable) import Control.Exception (Exception)--data Auth = Auth+import Network.HTTP.Types (Status, internalServerError500, unauthorized401)+import qualified Control.Monad.Trans.Writer    as Writer+import Control.Monad (void)+import Data.Kind (Type)  type AuthRoute = Route Auth +type MonadAuthHandler master m = (MonadHandler m, YesodAuth master, master ~ HandlerSite m, Auth ~ SubHandlerSite m, MonadUnliftIO m)+type AuthHandler master a = forall m. MonadAuthHandler master m => m a+ type Method = Text type Piece = Text +-- | The result of an authentication based on credentials+--+-- @since 1.4.4+data AuthenticationResult master+    = Authenticated (AuthId master) -- ^ Authenticated successfully+    | UserError AuthMessage         -- ^ Invalid credentials provided by user+    | ServerError Text              -- ^ Some other error+ data AuthPlugin master = AuthPlugin     { apName :: Text-    , apDispatch :: Method -> [Piece] -> GHandler Auth master ()-    , apLogin :: forall sub. (Route Auth -> Route master) -> GWidget sub master ()+    , apDispatch :: Method -> [Piece] -> AuthHandler master TypedContent+    , apLogin :: (Route Auth -> Route master) -> WidgetFor master ()     }  getAuth :: a -> Auth@@ -74,11 +107,15 @@     { credsPlugin :: Text -- ^ How the user was authenticated     , credsIdent :: Text -- ^ Identifier. Exact meaning depends on plugin.     , credsExtra :: [(Text, Text)]-    }+    } deriving (Show)  class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage) => YesodAuth master where     type AuthId master +    -- | specify the layout. Uses defaultLayout by default+    authLayout :: (MonadHandler m, HandlerSite m ~ master) => WidgetFor master () -> m Html+    authLayout = liftHandler . defaultLayout+     -- | Default destination on successful login, if no other     -- destination exists.     loginDest :: master -> Route master@@ -87,24 +124,59 @@     -- destination exists.     logoutDest :: master -> Route master +    -- | Perform authentication based on the given credentials.+    --+    -- Default implementation is in terms of @'getAuthId'@+    --+    -- @since: 1.4.4+    authenticate :: (MonadHandler m, HandlerSite m ~ master) => Creds master -> m (AuthenticationResult master)+    authenticate creds = do+        muid <- getAuthId creds++        return $ maybe (UserError Msg.InvalidLogin) Authenticated muid+     -- | Determine the ID associated with the set of credentials.-    getAuthId :: Creds master -> GHandler sub master (Maybe (AuthId master))+    --+    -- Default implementation is in terms of @'authenticate'@+    --+    getAuthId :: (MonadHandler m, HandlerSite m ~ master) => Creds master -> m (Maybe (AuthId master))+    getAuthId creds = do+        auth <- authenticate creds +        return $ case auth of+            Authenticated auid -> Just auid+            _ -> Nothing+     -- | Which authentication backends to use.     authPlugins :: master -> [AuthPlugin master]      -- | What to show on the login page.-    loginHandler :: GHandler Auth master RepHtml-    loginHandler = defaultLayout $ do-        setTitleI Msg.LoginTitle-        tm <- lift getRouteToMaster-        master <- lift getYesod-        mapM_ (flip apLogin tm) (authPlugins master)+    --+    -- By default this calls 'defaultLoginHandler', which concatenates+    -- plugin widgets and wraps the result in 'authLayout'. Override if+    -- you need fancy widget containers, additional functionality, or an+    -- entirely custom page.  For example, in some applications you may+    -- want to prevent the login page being displayed for a user who is+    -- already logged in, even if the URL is visited explicitly; this can+    -- be done by overriding 'loginHandler' in your instance declaration+    -- with something like:+    --+    -- > instance YesodAuth App where+    -- >     ...+    -- >     loginHandler = do+    -- >         ma <- lift maybeAuthId+    -- >         when (isJust ma) $+    -- >             lift $ redirect HomeR   -- or any other Handler code you want+    -- >         defaultLoginHandler+    --+    loginHandler :: AuthHandler master Html+    loginHandler = defaultLoginHandler      -- | Used for i18n of messages provided by this package.     renderAuthMessage :: master                       -> [Text] -- ^ languages-                      -> AuthMessage -> Text+                      -> AuthMessage+                      -> Text     renderAuthMessage _ _ = defaultMessage      -- | After login and logout, redirect to the referring page, instead of@@ -112,19 +184,27 @@     redirectToReferer :: master -> Bool     redirectToReferer _ = False +    -- | When being redirected to the login page should the current page+    -- be set to redirect back to. Default is 'True'.+    --+    -- @since 1.4.21+    redirectToCurrent :: master -> Bool+    redirectToCurrent _ = True+     -- | Return an HTTP connection manager that is stored in the foundation     -- type. This allows backends to reuse persistent connections. If none of     -- the backends you're using use HTTP connections, you can safely return-    -- @error \"authHttpManager"@ here.-    authHttpManager :: master -> Manager+    -- @error \"authHttpManager\"@ here.+    authHttpManager :: (MonadHandler m, HandlerSite m ~ master) => m Manager+    authHttpManager = liftIO getGlobalManager      -- | Called on a successful login. By default, calls-    -- @setMessageI NowLoggedIn@.-    onLogin :: GHandler sub master ()-    onLogin = setMessageI Msg.NowLoggedIn+    -- @addMessageI "success" NowLoggedIn@.+    onLogin :: (MonadHandler m, master ~ HandlerSite m) => m ()+    onLogin = addMessageI "success" Msg.NowLoggedIn      -- | Called on logout. By default, does nothing-    onLogout :: GHandler sub master ()+    onLogout :: (MonadHandler m, master ~ HandlerSite m) => m ()     onLogout = return ()      -- | Retrieves user credentials, if user is authenticated.@@ -135,79 +215,234 @@     -- especially useful for creating an API to be accessed via some means     -- other than a browser.     ---    -- Since 1.1.2-    maybeAuthId :: GHandler sub master (Maybe (AuthId master))+    -- @since 1.2.0+    maybeAuthId :: (MonadHandler m, master ~ HandlerSite m) => m (Maybe (AuthId master))++    default maybeAuthId+        :: (MonadHandler m, master ~ HandlerSite m, YesodAuthPersist master, Typeable (AuthEntity master))+        => m (Maybe (AuthId master))     maybeAuthId = defaultMaybeAuthId +    -- | Called on login error for HTTP requests. By default, calls+    -- @addMessage@ with "error" as status and redirects to @dest@.+    onErrorHtml :: (MonadHandler m, HandlerSite m ~ master) => Route master -> Text -> m Html+    onErrorHtml dest msg = do+        addMessage "error" $ toHtml msg+        fmap asHtml $ redirect dest++    -- | runHttpRequest gives you a chance to handle an HttpException and retry+    --  The default behavior is to simply execute the request which will throw an exception on failure+    --+    --  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+    runHttpRequest+      :: (MonadHandler m, HandlerSite m ~ master, MonadUnliftIO m)+      => Request+      -> (Response BodyReader -> m a)+      -> m a+    runHttpRequest req inner = do+      man <- authHttpManager+      withRunInIO $ \run -> withResponse req man $ run . inner++    {-# MINIMAL loginDest, logoutDest, (authenticate | getAuthId), authPlugins #-}++{-# DEPRECATED getAuthId "Define 'authenticate' instead; 'getAuthId' will be removed in the next major version" #-}++-- | Internal session key used to hold the authentication information.+--+-- @since 1.2.3 credsKey :: Text credsKey = "_ID"  -- | Retrieves user credentials from the session, if user is authenticated. ----- Since 1.1.2-defaultMaybeAuthId :: YesodAuth master-                   => GHandler sub master (Maybe (AuthId master))-defaultMaybeAuthId = do-    ms <- lookupSession credsKey-    case ms of-        Nothing -> return Nothing-        Just s -> return $ fromPathPiece s+-- This function does /not/ confirm that the credentials are valid, see+-- 'maybeAuthIdRaw' for more information. The first call in a request+-- does a database request to make sure that the account is still in the database.+--+-- @since 1.1.2+defaultMaybeAuthId+    :: (MonadHandler m, HandlerSite m ~ master, YesodAuthPersist master, Typeable (AuthEntity master))+    => m (Maybe (AuthId master))+defaultMaybeAuthId = runMaybeT $ do+    s   <- MaybeT $ lookupSession credsKey+    aid <- MaybeT $ return $ fromPathPiece s+    _   <- MaybeT $ cachedAuth aid+    return aid -mkYesodSub "Auth"-    [ ClassP ''YesodAuth [VarT $ mkName "master"]-    ]-#define STRINGS *Texts-    [parseRoutes|-/check                 CheckR      GET-/login                 LoginR      GET-/logout                LogoutR     GET POST-/page/#Text/STRINGS PluginR-|]+cachedAuth+    :: ( MonadHandler m+       , YesodAuthPersist master+       , Typeable (AuthEntity master)+       , HandlerSite m ~ master+       )+    => AuthId master+    -> m (Maybe (AuthEntity master))+cachedAuth+    = fmap unCachedMaybeAuth+    . cached+    . fmap CachedMaybeAuth+    . getAuthEntity --- | 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-         -> GHandler sub master ()-setCreds doRedirects creds = do++-- | Default handler to show the login page.+--+-- This is the default 'loginHandler'.  It concatenates plugin widgets and+-- wraps the result in 'authLayout'.  See 'loginHandler' for more details.+--+-- @since 1.4.9+defaultLoginHandler :: AuthHandler master Html+defaultLoginHandler = do+    tp <- getRouteToParent+    authLayout $ do+        setTitleI Msg.LoginTitle+        master <- getYesod+        mapM_ (flip apLogin tp) (authPlugins master)+++loginErrorMessageI+  :: Route Auth+  -> AuthMessage+  -> AuthHandler master TypedContent+loginErrorMessageI dest msg = do+  toParent <- getRouteToParent+  loginErrorMessageMasterI (toParent dest) msg+++loginErrorMessageMasterI+  :: (MonadHandler m, HandlerSite m ~ master, YesodAuth master)+  => Route master+  -> AuthMessage+  -> m TypedContent+loginErrorMessageMasterI dest msg = do+  mr <- getMessageRender+  loginErrorMessage dest (mr msg)++-- | For HTML, set the message and redirect to the route.+-- For JSON, send the message and a 401 status+loginErrorMessage+         :: (MonadHandler m, YesodAuth (HandlerSite m))+         => Route (HandlerSite m)+         -> Text+         -> m TypedContent+loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg)++messageJson401+  :: MonadHandler m+  => Text+  -> m Html+  -> m TypedContent+messageJson401 = messageJsonStatus unauthorized401++messageJson500 :: MonadHandler m => Text -> m Html -> m TypedContent+messageJson500 = messageJsonStatus internalServerError500++messageJsonStatus+  :: MonadHandler m+  => Status+  -> Text+  -> m Html+  -> m TypedContent+messageJsonStatus status msg html = selectRep $ do+    provideRep html+    provideRep $ do+        let obj = object ["message" .= msg]+        void $ sendResponseStatus status obj+        return obj++provideJsonMessage :: Monad m => Text -> Writer.Writer (Endo [ProvidedRep m]) ()+provideJsonMessage msg = provideRep $ return $ object ["message" .= msg]+++setCredsRedirect+  :: (MonadHandler m, YesodAuth (HandlerSite m))+  => Creds (HandlerSite m) -- ^ new credentials+  -> m TypedContent+setCredsRedirect creds = do     y    <- getYesod-    maid <- getAuthId creds-    case maid of-        Nothing ->-          when doRedirects $ do-            case authRoute y of-              Nothing -> do rh <- defaultLayout $ toWidget [shamlet|-$newline never-<h1>Invalid login-|]-                            sendResponse rh-              Just ar -> do setMessageI Msg.InvalidLogin-                            redirect ar-        Just aid -> do+    auth <- authenticate creds+    case auth of+        Authenticated aid -> do             setSession credsKey $ toPathPiece aid-            when doRedirects $ do-              onLogin-              redirectUltDest $ loginDest y+            onLogin+            res <- selectRep $ do+                provideRepType typeHtml $+                    fmap asHtml $ redirectUltDest $ loginDest y+                provideJsonMessage "Login Successful"+            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.+setCreds :: (MonadHandler m, YesodAuth (HandlerSite m))+         => Bool                  -- ^ if HTTP redirects should be done+         -> Creds (HandlerSite m) -- ^ new credentials+         -> m ()+setCreds doRedirects creds =+    if doRedirects+      then void $ setCredsRedirect creds+      else do auth <- authenticate creds+              case auth of+                  Authenticated aid -> setSession credsKey $ toPathPiece aid+                  _ -> return ()++-- | same as defaultLayoutJson, but uses authLayout+authLayoutJson+  :: (ToJSON j, MonadAuthHandler master m)+  => WidgetFor master ()  -- ^ HTML+  -> m j  -- ^ JSON+  -> m TypedContent+authLayoutJson w json = selectRep $ do+    provideRep $ authLayout w+    provideRep $ fmap toJSON json+ -- | Clears current user credentials for the session. ----- Since 1.1.7-clearCreds :: YesodAuth master-           => Bool -- ^ if HTTP redirect to 'logoutDest' should be done-           -> GHandler sub master ()+-- @since 1.1.7+clearCreds :: (MonadHandler m, YesodAuth (HandlerSite m))+           => Bool -- ^ if HTTP, redirect to 'logoutDest'+           -> m () clearCreds doRedirects = do-    y <- getYesod+    onLogout     deleteSession credsKey-    when doRedirects $ do-        onLogout-        redirectUltDest $ logoutDest y+    y  <- getYesod+    aj <- acceptsJson+    case (aj, doRedirects) of+      (True, _)               -> sendResponse successfulLogout+      (False, True)           -> redirectUltDest (logoutDest y)+      _                       -> return ()+    where successfulLogout = object ["message" .= msg]+          msg :: Text+          msg = "Logged out successfully!" -getCheckR :: YesodAuth master => GHandler Auth master RepHtmlJson+getCheckR :: AuthHandler master TypedContent getCheckR = do     creds <- maybeAuthId-    defaultLayoutJson (do+    authLayoutJson (do         setTitle "Authentication Status"-        toWidget $ html' creds) (jsonCreds creds)+        toWidget $ html' creds) (return $ jsonCreds creds)   where     html' creds =         [shamlet|@@ -219,27 +454,27 @@     <p>Not logged in. |]     jsonCreds creds =-        Object $ Map.fromList+        toJSON $ Map.fromList             [ (T.pack "logged_in", Bool $ maybe False (const True) creds)             ] -setUltDestReferer' :: YesodAuth master => GHandler sub master ()+setUltDestReferer' :: (MonadHandler m, YesodAuth (HandlerSite m)) => m () setUltDestReferer' = do     master <- getYesod     when (redirectToReferer master) setUltDestReferer -getLoginR :: YesodAuth master => GHandler Auth master RepHtml+getLoginR :: AuthHandler master Html getLoginR = setUltDestReferer' >> loginHandler -getLogoutR :: YesodAuth master => GHandler Auth master ()+getLogoutR :: AuthHandler master () getLogoutR = do-    tm <- getRouteToMaster-    setUltDestReferer' >> redirectToPost (tm LogoutR)+  tp <- getRouteToParent+  setUltDestReferer' >> redirectToPost (tp LogoutR) -postLogoutR :: YesodAuth master => GHandler Auth master ()+postLogoutR :: AuthHandler master () postLogoutR = clearCreds True -handlePluginR :: YesodAuth master => Text -> [Text] -> GHandler Auth master ()+handlePluginR :: Text -> [Text] -> AuthHandler master TypedContent handlePluginR plugin pieces = do     master <- getYesod     env <- waiRequest@@ -248,49 +483,122 @@         [] -> notFound         ap:_ -> apDispatch ap method pieces -maybeAuth :: ( YesodAuth master-#if MIN_VERSION_persistent(1, 1, 0)-             , PersistMonadBackend (b (GHandler sub master)) ~ PersistEntityBackend val-             , b ~ YesodPersistBackend master+-- | Similar to 'maybeAuthId', but additionally look up the value associated+-- with the user\'s database identifier to get the value in the database. This+-- assumes that you are using a Persistent database.+--+-- @since 1.1.0+maybeAuth :: ( YesodAuthPersist master+             , val ~ AuthEntity master              , Key val ~ AuthId master-             , PersistStore (b (GHandler sub master))-#else-             , b ~ YesodPersistBackend master-             , b ~ PersistEntityBackend val-             , Key b val ~ AuthId master-             , PersistStore b (GHandler sub master)-#endif              , PersistEntity val-             , YesodPersist master-             ) => GHandler sub master (Maybe (Entity val))-maybeAuth = runMaybeT $ do-    aid <- MaybeT $ maybeAuthId-    a   <- MaybeT $ runDB $ get aid-    return $ Entity aid a+             , Typeable val+             , MonadHandler m+             , HandlerSite m ~ master+             ) => m (Maybe (Entity val))+maybeAuth = fmap (fmap (uncurry Entity)) maybeAuthPair -requireAuthId :: YesodAuth master => GHandler sub master (AuthId master)-requireAuthId = maybeAuthId >>= maybe redirectLogin return+-- | Similar to 'maybeAuth', but doesn’t assume that you are using a+-- Persistent database.+--+-- @since 1.4.0+maybeAuthPair+  :: ( YesodAuthPersist master+     , Typeable (AuthEntity master)+     , MonadHandler m+     , HandlerSite m ~ master+     )+  => m (Maybe (AuthId master, AuthEntity master))+maybeAuthPair = runMaybeT $ do+    aid <- MaybeT maybeAuthId+    ae  <- MaybeT $ cachedAuth aid+    return (aid, ae) -requireAuth :: ( YesodAuth master-               , b ~ YesodPersistBackend master-#if MIN_VERSION_persistent(1, 1, 0)-               , PersistMonadBackend (b (GHandler sub master)) ~ PersistEntityBackend val++newtype CachedMaybeAuth val = CachedMaybeAuth { unCachedMaybeAuth :: Maybe val }++-- | Class which states that the given site is an instance of @YesodAuth@+-- and that its @AuthId@ is a lookup key for the full user information in+-- a @YesodPersist@ database.+--+-- The default implementation of @getAuthEntity@ assumes that the @AuthId@+-- for the @YesodAuth@ superclass is in fact a persistent @Key@ for the+-- given value.  This is the common case in Yesod, and means that you can+-- easily look up the full information on a given user.+--+-- @since 1.4.0+class (YesodAuth master, YesodPersist master) => YesodAuthPersist master where+    -- | If the @AuthId@ for a given site is a persistent ID, this will give the+    -- value for that entity. E.g.:+    --+    -- > type AuthId MySite = UserId+    -- > AuthEntity MySite ~ User+    --+    -- @since 1.2.0+    type AuthEntity master :: Type+    type AuthEntity master = KeyEntity (AuthId master)++    getAuthEntity :: (MonadHandler m, HandlerSite m ~ master)+                  => AuthId master -> m (Maybe (AuthEntity master))++    default getAuthEntity+        :: ( YesodPersistBackend master ~ backend+           , PersistRecordBackend (AuthEntity master) backend+           , Key (AuthEntity master) ~ AuthId master+           , PersistStore backend+           , MonadHandler m+           , HandlerSite m ~ master+           )+        => AuthId master -> m (Maybe (AuthEntity master))+    getAuthEntity = liftHandler . runDB . get+++type family KeyEntity key+type instance KeyEntity (Key x) = x++-- | Similar to 'maybeAuthId', but redirects to a login page if user is not+-- authenticated or responds with error 401 if this is an API client (expecting JSON).+--+-- @since 1.1.0+requireAuthId :: (MonadHandler m, YesodAuth (HandlerSite m)) => m (AuthId (HandlerSite m))+requireAuthId = maybeAuthId >>= maybe handleAuthLack return++-- | Similar to 'maybeAuth', but redirects to a login page if user is not+-- authenticated or responds with error 401 if this is an API client (expecting JSON).+--+-- @since 1.1.0+requireAuth :: ( YesodAuthPersist master+               , val ~ AuthEntity master                , Key val ~ AuthId master-               , PersistStore (b (GHandler sub master))-#else-               , b ~ PersistEntityBackend val-               , Key b val ~ AuthId master-               , PersistStore b (GHandler sub master)-#endif                , PersistEntity val-               , YesodPersist master-               ) => GHandler sub master (Entity val)-requireAuth = maybeAuth >>= maybe redirectLogin return+               , Typeable val+               , MonadHandler m+               , HandlerSite m ~ master+               ) => m (Entity val)+requireAuth = maybeAuth >>= maybe handleAuthLack return -redirectLogin :: Yesod master => GHandler sub master a+-- | Similar to 'requireAuth', but not tied to Persistent's 'Entity' type.+-- Instead, the 'AuthId' and 'AuthEntity' are returned in a tuple.+--+-- @since 1.4.0+requireAuthPair+  :: ( YesodAuthPersist master+     , Typeable (AuthEntity master)+     , MonadHandler m+     , HandlerSite m ~ master+     )+  => m (AuthId master, AuthEntity master)+requireAuthPair = maybeAuthPair >>= maybe handleAuthLack return++handleAuthLack :: (YesodAuth (HandlerSite m), MonadHandler m) => m a+handleAuthLack = do+    aj <- acceptsJson+    if aj then notAuthenticated else redirectLogin++redirectLogin :: (YesodAuth (HandlerSite m), MonadHandler m) => m a redirectLogin = do     y <- getYesod-    setUltDestCurrent+    when (redirectToCurrent y) setUltDestCurrent     case authRoute y of         Just z -> redirect z         Nothing -> permissionDenied "Please configure authRoute"@@ -298,7 +606,12 @@ instance YesodAuth master => RenderMessage master AuthMessage where     renderMessage = renderAuthMessage -data AuthException = InvalidBrowserIDAssertion-                   | InvalidFacebookResponse-    deriving (Show, Typeable)+data AuthException = InvalidFacebookResponse+    deriving Show instance Exception AuthException++instance YesodAuth master => YesodSubDispatch Auth master where+    yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth)++asHtml :: Html -> Html+asHtml = id
Yesod/Auth/BrowserId.hs view
@@ -1,66 +1,91 @@-{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-}++-- | NOTE: Mozilla Persona will be shut down by the end of 2016, therefore this+-- module is no longer recommended for use. module Yesod.Auth.BrowserId+    {-# DEPRECATED "Mozilla Persona will be shut down by the end of 2016" #-}     ( authBrowserId-    , authBrowserIdAudience-    , createOnClick+    , createOnClick, createOnClickOverride+    , def+    , BrowserIdSettings+    , bisAudience+    , bisLazyLoad+    , forwardUrl     ) where  import Yesod.Auth import Web.Authenticate.BrowserId import Data.Text (Text) import Yesod.Core-import Text.Hamlet (hamlet) import qualified Data.Text as T import Data.Maybe (fromMaybe)-import Control.Monad.IO.Class (liftIO)-import Control.Monad (when)-import Control.Exception (throwIO)-import Text.Julius (julius, rawJS)-import Data.Aeson (toJSON)+import Control.Monad (when, unless)+import Text.Julius (rawJS) import Network.URI (uriPath, parseURI) import Data.FileEmbed (embedFile) import Data.ByteString (ByteString)+import Data.Default  pid :: Text pid = "browserid" -complete :: Route Auth-complete = PluginR pid []+forwardUrl :: AuthRoute+forwardUrl = PluginR pid [] --- | Log into browser ID with an audience value determined from the 'approot'.-authBrowserId :: YesodAuth m => AuthPlugin m-authBrowserId = helper Nothing+complete :: AuthRoute+complete = forwardUrl --- | Log into browser ID with the given audience value. Note that this must be--- your actual hostname, or login will fail.-authBrowserIdAudience-    :: YesodAuth m-    => Text -- ^ audience-    -> AuthPlugin m-authBrowserIdAudience = helper . Just+-- | A settings type for various configuration options relevant to BrowserID.+--+-- See: <http://www.yesodweb.com/book/settings-types>+--+-- Since 1.2.0+data BrowserIdSettings = BrowserIdSettings+    { bisAudience :: Maybe Text+    -- ^ BrowserID audience value. If @Nothing@, will be extracted based on the+    -- approot.+    --+    -- Default: @Nothing@+    --+    -- Since 1.2.0+    , bisLazyLoad :: Bool+    -- ^ Use asynchronous Javascript loading for the BrowserID JS file.+    --+    -- Default: @True@.+    --+    -- Since 1.2.0+    } -helper :: YesodAuth m-       => Maybe Text -- ^ audience-       -> AuthPlugin m-helper maudience = AuthPlugin+instance Default BrowserIdSettings where+    def = BrowserIdSettings+        { bisAudience = Nothing+        , bisLazyLoad = True+        }++authBrowserId :: YesodAuth m => BrowserIdSettings -> AuthPlugin m+authBrowserId bis@BrowserIdSettings {..} = AuthPlugin     { apName = pid     , apDispatch = \m ps ->         case (m, ps) of             ("GET", [assertion]) -> do-                master <- getYesod                 audience <--                    case maudience of+                    case bisAudience of                         Just a -> return a                         Nothing -> do-                            tm <- getRouteToMaster                             r <- getUrlRender+                            tm <- getRouteToParent                             return $ T.takeWhile (/= '/') $ stripScheme $ r $ tm LoginR-                memail <- lift $ checkAssertion audience assertion (authHttpManager master)+                manager <- authHttpManager+                memail <- checkAssertion audience assertion manager                 case memail of-                    Nothing -> liftIO $ throwIO InvalidBrowserIDAssertion-                    Just email -> setCreds True Creds+                    Nothing -> do+                      $logErrorS "yesod-auth" "BrowserID assertion failure"+                      tm <- getRouteToParent+                      loginErrorMessage (tm LoginR) "BrowserID login error."+                    Just email -> setCredsRedirect Creds                         { credsPlugin = pid                         , credsIdent = email                         , credsExtra = []@@ -72,12 +97,10 @@             (_, []) -> badMethod             _ -> notFound     , apLogin = \toMaster -> do-        onclick <- createOnClick toMaster+        onclick <- createOnClick bis toMaster -        autologin <- fmap (== Just "true") $ lift $ lookupGetParam "autologin"-        when autologin $ toWidget [julius|-#{rawJS onclick}();-|]+        autologin <- fmap (== Just "true") $ lookupGetParam "autologin"+        when autologin $ toWidget [julius|#{rawJS onclick}();|]          toWidget [hamlet| $newline never@@ -92,32 +115,57 @@  -- | Generates a function to handle on-click events, and returns that function -- name.-createOnClick :: (Route Auth -> Route master) -> GWidget sub master Text-createOnClick toMaster = do-    addScriptRemote browserIdJs-    onclick <- lift newIdent-    render <- lift getUrlRender-    let login = toJSON $ getPath $ render (toMaster LoginR)+createOnClickOverride :: BrowserIdSettings+              -> (Route Auth -> Route master)+              -> Maybe (Route master)+              -> WidgetFor master Text+createOnClickOverride BrowserIdSettings {..} toMaster mOnRegistration = do+    unless bisLazyLoad $ addScriptRemote browserIdJs+    onclick <- newIdent+    render <- getUrlRender+    let login = toJSON $ getPath $ render loginRoute -- (toMaster LoginR)+        loginRoute = maybe (toMaster LoginR) id mOnRegistration     toWidget [julius|         function #{rawJS onclick}() {-            navigator.id.watch({-                onlogin: function (assertion) {-                    if (assertion) {-                        document.location = "@{toMaster complete}/" + assertion;-                    }-                },-                onlogout: function () {}-            });-            navigator.id.request({-                returnTo: #{login} + "?autologin=true"-            });+            if (navigator.id) {+                navigator.id.watch({+                    onlogin: function (assertion) {+                        if (assertion) {+                            document.location = "@{toMaster complete}/" + assertion;+                        }+                    },+                    onlogout: function () {}+                });+                navigator.id.request({+                    returnTo: #{login} + "?autologin=true"+                });+            }+            else {+                alert("Loading, please try again");+            }         }     |]+    when bisLazyLoad $ toWidget [julius|+        (function(){+            var bid = document.createElement("script");+            bid.async = true;+            bid.src = #{toJSON browserIdJs};+            var s = document.getElementsByTagName('script')[0];+            s.parentNode.insertBefore(bid, s);+        })();+    |] -    autologin <- fmap (== Just "true") $ lift $ lookupGetParam "autologin"+    autologin <- fmap (== Just "true") $ lookupGetParam "autologin"     when autologin $ toWidget [julius|#{rawJS onclick}();|]     return onclick   where     getPath t = fromMaybe t $ do         uri <- parseURI $ T.unpack t         return $ T.pack $ uriPath uri++-- | Generates a function to handle on-click events, and returns that function+-- name.+createOnClick :: BrowserIdSettings+              -> (Route Auth -> Route master)+              -> WidgetFor master Text+createOnClick bidSettings toMaster = createOnClickOverride bidSettings toMaster Nothing
Yesod/Auth/Dummy.hs view
@@ -1,31 +1,77 @@-{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+ -- | Provides a dummy authentication module that simply lets a user specify--- his/her identifier. This is not intended for real world use, just for--- testing.+-- their identifier. This is not intended for real world use, just for+-- testing. This plugin supports form submissions via JSON (since 1.6.8).+--+-- = Using the JSON Login Endpoint+--+-- We are assuming that you have declared `authRoute` as follows+--+-- @+--       Just $ AuthR LoginR+-- @+--+-- If you are using a different one, then you have to adjust the+-- endpoint accordingly.+--+-- @+--       Endpoint: \/auth\/page\/dummy+--       Method: POST+--       JSON Data: {+--                      "ident": "my identifier"+--                  }+-- @+--+-- Remember to add the following headers:+--+--     - Accept: application\/json+--     - Content-Type: application\/json+ module Yesod.Auth.Dummy     ( authDummy     ) where -import Yesod.Auth-import Yesod.Form (runInputPost, textField, ireq)-import Yesod.Handler (notFound)-import Text.Hamlet (hamlet)-import Yesod.Widget (toWidget)+import           Data.Aeson.Types (Parser, Result (..))+import qualified Data.Aeson.Types as A (parseEither, withObject)+import           Data.Text        (Text)+import           Yesod.Auth+import           Yesod.Core+import           Yesod.Form       (ireq, runInputPost, textField) +identParser :: Value -> Parser Text+identParser = A.withObject "Ident" (.: "ident")+ authDummy :: YesodAuth m => AuthPlugin m authDummy =     AuthPlugin "dummy" dispatch login   where+    dispatch :: Text -> [Text] -> AuthHandler m TypedContent     dispatch "POST" [] = do-        ident <- runInputPost $ ireq textField "ident"-        setCreds True $ Creds "dummy" ident []+        (jsonResult :: Result Value) <- parseCheckJsonBody+        eIdent <- case jsonResult of+            Success val -> return $ A.parseEither identParser val+            Error   err -> return $ Left err+        case eIdent of+            Right ident ->+                setCredsRedirect $ Creds "dummy" ident []+            Left  _     -> do+                ident <- runInputPost $ ireq textField "ident"+                setCredsRedirect $ Creds "dummy" ident []     dispatch _ _ = notFound     url = PluginR "dummy" []-    login authToMaster =+    login authToMaster = do+        request <- getRequest         toWidget [hamlet| $newline never <form method="post" action="@{authToMaster url}">+    $maybe t <- reqToken request+        <input type=hidden name=#{defaultCsrfParamName} value=#{t}>     Your new identifier is: #     <input type="text" name="ident">     <input type="submit" value="Dummy Login">
Yesod/Auth/Email.hs view
@@ -1,283 +1,999 @@-{-# LANGUAGE QuasiQuotes, TypeFamilies #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE FlexibleContexts #-}-module Yesod.Auth.Email-    ( -- * Plugin-      authEmail-    , YesodAuthEmail (..)-    , EmailCreds (..)-    , saltPass-      -- * Routes-    , loginR-    , registerR-    , setpassR-    , isValidPass-    ) where--import Network.Mail.Mime (randomString)-import Yesod.Auth-import System.Random-import Control.Monad (when)-import Control.Applicative ((<$>), (<*>))-import Data.Digest.Pure.MD5-import qualified Data.Text.Lazy as T-import qualified Data.Text as TS-import Data.Text.Lazy.Encoding (encodeUtf8)-import Data.Text (Text)-import qualified Crypto.PasswordStore as PS-import qualified Data.Text.Encoding as DTE--import Yesod.Form-import Yesod.Handler-import Yesod.Content-import Yesod.Core (PathPiece, fromPathPiece, whamlet, defaultLayout, setTitleI, toPathPiece)-import Control.Monad.IO.Class (liftIO)-import qualified Yesod.Auth.Message as Msg--loginR, registerR, setpassR :: AuthRoute-loginR = PluginR "email" ["login"]-registerR = PluginR "email" ["register"]-setpassR = PluginR "email" ["set-password"]--verify :: Text -> Text -> AuthRoute -- FIXME-verify eid verkey = PluginR "email" ["verify", eid, verkey]--type Email = Text-type VerKey = Text-type VerUrl = Text-type SaltedPass = Text-type VerStatus = Bool---- | Data stored in a database for each e-mail address.-data EmailCreds m = EmailCreds-    { emailCredsId :: AuthEmailId m-    , emailCredsAuthId :: Maybe (AuthId m)-    , emailCredsStatus :: VerStatus-    , emailCredsVerkey :: Maybe VerKey-    }--class (YesodAuth m, PathPiece (AuthEmailId m)) => YesodAuthEmail m where-    type AuthEmailId m--    addUnverified :: Email -> VerKey -> GHandler Auth m (AuthEmailId m)-    sendVerifyEmail :: Email -> VerKey -> VerUrl -> GHandler Auth m ()-    getVerifyKey :: AuthEmailId m -> GHandler Auth m (Maybe VerKey)-    setVerifyKey :: AuthEmailId m -> VerKey -> GHandler Auth m ()-    verifyAccount :: AuthEmailId m -> GHandler Auth m (Maybe (AuthId m))-    getPassword :: AuthId m -> GHandler Auth m (Maybe SaltedPass)-    setPassword :: AuthId m -> SaltedPass -> GHandler Auth m ()-    getEmailCreds :: Email -> GHandler Auth m (Maybe (EmailCreds m))-    getEmail :: AuthEmailId m -> GHandler Auth m (Maybe Email)--    -- | Generate a random alphanumeric string.-    randomKey :: m -> IO Text-    randomKey _ = do-        stdgen <- newStdGen-        return $ TS.pack $ fst $ randomString 10 stdgen--authEmail :: YesodAuthEmail m => AuthPlugin m-authEmail =-    AuthPlugin "email" dispatch $ \tm ->-        [whamlet|-$newline never-<form method="post" action="@{tm loginR}">-    <table>-        <tr>-            <th>_{Msg.Email}-            <td>-                <input type="email" name="email">-        <tr>-            <th>_{Msg.Password}-            <td>-                <input type="password" name="password">-        <tr>-            <td colspan="2">-                <input type="submit" value=_{Msg.LoginViaEmail}>-                <a href="@{tm registerR}">I don't have an account-|]-  where-    dispatch "GET" ["register"] = getRegisterR >>= sendResponse-    dispatch "POST" ["register"] = postRegisterR >>= sendResponse-    dispatch "GET" ["verify", eid, verkey] =-        case fromPathPiece eid of-            Nothing -> notFound-            Just eid' -> getVerifyR eid' verkey >>= sendResponse-    dispatch "POST" ["login"] = postLoginR >>= sendResponse-    dispatch "GET" ["set-password"] = getPasswordR >>= sendResponse-    dispatch "POST" ["set-password"] = postPasswordR >>= sendResponse-    dispatch _ _ = notFound--getRegisterR :: YesodAuthEmail master => GHandler Auth master RepHtml-getRegisterR = do-    toMaster <- getRouteToMaster-    email <- newIdent-    defaultLayout $ do-        setTitleI Msg.RegisterLong-        [whamlet|-$newline never-<p>_{Msg.EnterEmail}-<form method="post" action="@{toMaster registerR}">-    <label for=#{email}>_{Msg.Email}-    <input ##{email} type="email" name="email" width="150">-    <input type="submit" value=_{Msg.Register}>-|]--postRegisterR :: YesodAuthEmail master => GHandler Auth master RepHtml-postRegisterR = do-    y <- getYesod-    email <- runInputPost $ ireq emailField "email"-    mecreds <- getEmailCreds email-    (lid, verKey) <--        case mecreds of-            Just (EmailCreds lid _ _ (Just key)) -> return (lid, key)-            Just (EmailCreds lid _ _ Nothing) -> do-                key <- liftIO $ randomKey y-                setVerifyKey lid key-                return (lid, key)-            Nothing -> do-                key <- liftIO $ randomKey y-                lid <- addUnverified email key-                return (lid, key)-    render <- getUrlRender-    tm <- getRouteToMaster-    let verUrl = render $ tm $ verify (toPathPiece lid) verKey-    sendVerifyEmail email verKey verUrl-    defaultLayout $ do-        setTitleI Msg.ConfirmationEmailSentTitle-        [whamlet|-$newline never-<p>_{Msg.ConfirmationEmailSent email}-|]--getVerifyR :: YesodAuthEmail m-           => AuthEmailId m -> Text -> GHandler Auth m RepHtml-getVerifyR lid key = do-    realKey <- getVerifyKey lid-    memail <- getEmail lid-    case (realKey == Just key, memail) of-        (True, Just email) -> do-            muid <- verifyAccount lid-            case muid of-                Nothing -> return ()-                Just _uid -> do-                    setCreds False $ Creds "email" email [("verifiedEmail", email)] -- FIXME uid?-                    toMaster <- getRouteToMaster-                    setMessageI Msg.AddressVerified-                    redirect $ toMaster setpassR-        _ -> return ()-    defaultLayout $ do-        setTitleI Msg.InvalidKey-        [whamlet|-$newline never-<p>_{Msg.InvalidKey}-|]--postLoginR :: YesodAuthEmail master => GHandler Auth master ()-postLoginR = do-    (email, pass) <- runInputPost $ (,)-        <$> ireq emailField "email"-        <*> ireq textField "password"-    mecreds <- getEmailCreds email-    maid <--        case (mecreds >>= emailCredsAuthId, fmap emailCredsStatus mecreds) of-            (Just aid, Just True) -> do-                mrealpass <- getPassword aid-                case mrealpass of-                    Nothing -> return Nothing-                    Just realpass -> return $-                        if isValidPass pass realpass-                            then Just aid-                            else Nothing-            _ -> return Nothing-    case maid of-        Just _aid ->-            setCreds True $ Creds "email" email [("verifiedEmail", email)] -- FIXME aid?-        Nothing -> do-            setMessageI Msg.InvalidEmailPass-            toMaster <- getRouteToMaster-            redirect $ toMaster LoginR--getPasswordR :: YesodAuthEmail master => GHandler Auth master RepHtml-getPasswordR = do-    toMaster <- getRouteToMaster-    maid <- maybeAuthId-    pass1 <- newIdent-    pass2 <- newIdent-    case maid of-        Just _ -> return ()-        Nothing -> do-            setMessageI Msg.BadSetPass-            redirect $ toMaster LoginR-    defaultLayout $ do-        setTitleI Msg.SetPassTitle-        [whamlet|-$newline never-<h3>_{Msg.SetPass}-<form method="post" action="@{toMaster setpassR}">-    <table>-        <tr>-            <th>-                <label for=#{pass1}>_{Msg.NewPass}-            <td>-                <input ##{pass1} type="password" name="new">-        <tr>-            <th>-                <label for=#{pass2}>_{Msg.ConfirmPass}-            <td>-                <input ##{pass2} type="password" name="confirm">-        <tr>-            <td colspan="2">-                <input type="submit" value="_{Msg.SetPassTitle}">-|]--postPasswordR :: YesodAuthEmail master => GHandler Auth master ()-postPasswordR = do-    (new, confirm) <- runInputPost $ (,)-        <$> ireq textField "new"-        <*> ireq textField "confirm"-    toMaster <- getRouteToMaster-    y <- getYesod-    when (new /= confirm) $ do-        setMessageI Msg.PassMismatch-        redirect $ toMaster setpassR-    maid <- maybeAuthId-    aid <- case maid of-            Nothing -> do-                setMessageI Msg.BadSetPass-                redirect $ toMaster LoginR-            Just aid -> return aid-    salted <- liftIO $ saltPass new-    setPassword aid salted-    setMessageI Msg.PassUpdated-    redirect $ loginDest y--saltLength :: Int-saltLength = 5---- | Salt a password with a randomly generated salt.-saltPass :: Text -> IO Text-saltPass = fmap DTE.decodeUtf8-         . flip PS.makePassword 12-         . DTE.encodeUtf8--saltPass' :: String -> String -> String-saltPass' salt pass =-    salt ++ show (md5 $ fromString $ salt ++ pass)-  where-    fromString = encodeUtf8 . T.pack--isValidPass :: Text -- ^ cleartext password-            -> SaltedPass -- ^ salted password-            -> Bool-isValidPass ct salted =-    PS.verifyPassword (DTE.encodeUtf8 ct) (DTE.encodeUtf8 salted) || isValidPass' ct salted--isValidPass' :: Text -- ^ cleartext password-            -> SaltedPass -- ^ salted password-            -> Bool-isValidPass' clear' salted' =-    let salt = take saltLength salted-     in salted == saltPass' salt clear-  where-    clear = TS.unpack clear'-    salted = TS.unpack salted'+{-# LANGUAGE ConstrainedClassMethods #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++-- | A Yesod plugin for Authentication via e-mail+--+-- This plugin works out of the box by only setting a few methods on+-- the type class that tell the plugin how to interoperate with your+-- user data storage (your database).  However, almost everything is+-- customizeable by setting more methods on the type class.  In+-- addition, you can send all the form submissions via JSON and+-- completely control the user's flow.+--+-- This is a standard registration e-mail flow+--+-- 1. A user registers a new e-mail address, and an e-mail is sent there+-- 2. The user clicks on the registration link in the e-mail. Note that+--   at this point they are actually logged in (without a+--   password). That means that when they log out they will need to+--  reset their password.+-- 3. The user sets their password and is redirected to the site.+-- 4. The user can now+--+--     * logout and sign in+--     * reset their password+--+-- = Using JSON Endpoints+--+-- We are assuming that you have declared auth route as follows+--+-- @+--    /auth AuthR Auth getAuth+-- @+--+-- If you are using a different route, then you have to adjust the+-- endpoints accordingly.+--+--     * Registration+--+-- @+--       Endpoint: \/auth\/page\/email\/register+--       Method: POST+--       JSON Data: {+--                      "email": "myemail@domain.com",+--                      "password": "myStrongPassword" (optional)+--                  }+-- @+--+--     * Forgot password+--+-- @+--       Endpoint: \/auth\/page\/email\/forgot-password+--       Method: POST+--       JSON Data: { "email": "myemail@domain.com" }+-- @+--+--     * Login+--+-- @+--       Endpoint: \/auth\/page\/email\/login+--       Method: POST+--       JSON Data: {+--                      "email": "myemail@domain.com",+--                      "password": "myStrongPassword"+--                  }+-- @+--+--     * Set new password+--+-- @+--       Endpoint: \/auth\/page\/email\/set-password+--       Method: POST+--       JSON Data: {+--                       "new": "newPassword",+--                       "confirm": "newPassword",+--                       "current": "currentPassword"+--                  }+-- @+--+--  Note that in the set password endpoint, the presence of the key+--  "current" is dependent on how the 'needOldPassword' is defined in+--  the instance for 'YesodAuthEmail'.++module Yesod.Auth.Email+    ( -- * Plugin+      authEmail+    , YesodAuthEmail (..)+    , EmailCreds (..)+    , saltPass+      -- * Routes+    , loginR+    , registerR+    , forgotPasswordR+    , setpassR+    , verifyR+    , isValidPass+      -- * Types+    , Email+    , VerKey+    , VerUrl+    , SaltedPass+    , VerStatus+    , Identifier+     -- * Misc+    , loginLinkKey+    , setLoginLinkKey+     -- * Default handlers+    , defaultEmailLoginHandler+    , defaultRegisterHandler+    , defaultForgotPasswordHandler+    , defaultSetPasswordHandler+     -- * Default helpers+    , defaultRegisterHelper+    ) where++import qualified Crypto.Hash                   as H+import qualified Crypto.Nonce                  as Nonce+import           Data.Aeson.Types              (Parser, Result (..), parseMaybe,+                                                withObject, (.:?))+import           Data.ByteArray                (convert)+import           Data.ByteString.Base16        as B16+import           Data.Maybe                    (isJust)+import           Data.Text                     (Text)+import qualified Data.Text                     as T+import qualified Data.Text                     as TS+import           Data.Text.Encoding            (decodeUtf8With, encodeUtf8)+import qualified Data.Text.Encoding            as TE+import           Data.Text.Encoding.Error      (lenientDecode)+import           Data.Time                     (addUTCTime, getCurrentTime)+import           Safe                          (readMay)+import           System.IO.Unsafe              (unsafePerformIO)+import qualified Text.Email.Validate+import           Yesod.Auth+import qualified Yesod.Auth.Message            as Msg+import qualified Yesod.Auth.Util.PasswordStore as PS+import           Yesod.Core+import           Yesod.Form++loginR, registerR, forgotPasswordR, setpassR :: AuthRoute+loginR = PluginR "email" ["login"]+registerR = PluginR "email" ["register"]+forgotPasswordR = PluginR "email" ["forgot-password"]+setpassR = PluginR "email" ["set-password"]++verifyURLHasSetPassText :: Text+verifyURLHasSetPassText = "has-set-pass"++-- |+--+-- @since 1.4.5+verifyR :: Text -> Text -> Bool -> AuthRoute -- FIXME+verifyR eid verkey hasSetPass = PluginR "email" path+    where path = "verify":eid:verkey:(if hasSetPass then [verifyURLHasSetPassText] else [])++type Email = Text+type VerKey = Text+type VerUrl = Text+type SaltedPass = Text+type VerStatus = Bool++-- | An Identifier generalizes an email address to allow users to log in with+-- some other form of credentials (e.g., username).+--+-- Note that any of these other identifiers must not be valid email addresses.+--+-- @since 1.2.0+type Identifier = Text++-- | Data stored in a database for each e-mail address.+data EmailCreds site = EmailCreds+    { emailCredsId     :: AuthEmailId site+    , emailCredsAuthId :: Maybe (AuthId site)+    , emailCredsStatus :: VerStatus+    , emailCredsVerkey :: Maybe VerKey+    , emailCredsEmail  :: Email+    }++data ForgotPasswordForm = ForgotPasswordForm { _forgotEmail :: Text }+data PasswordForm = PasswordForm { _passwordCurrent :: Text, _passwordNew :: Text, _passwordConfirm :: Text }+data UserForm = UserForm { _userFormEmail :: Text }+data UserLoginForm = UserLoginForm { _loginEmail :: Text, _loginPassword :: Text }++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+    -- has not yet been verified.+    --+    -- @since 1.1.0+    addUnverified :: Email -> VerKey -> AuthHandler site (AuthEmailId site)++    -- | Similar to `addUnverified`, but comes with the registered password.+    --+    -- The default implementation is just `addUnverified`, which ignores the password.+    --+    -- You may override this to save the salted password to your database.+    --+    -- @since 1.6.4+    addUnverifiedWithPass :: Email -> VerKey -> SaltedPass -> AuthHandler site (AuthEmailId site)+    addUnverifiedWithPass email verkey _ = addUnverified email verkey++    -- | Send an email to the given address to verify ownership.+    --+    -- @since 1.1.0+    sendVerifyEmail :: Email -> VerKey -> VerUrl -> AuthHandler site ()++    -- | Send an email to the given address to re-verify ownership in the case of+    -- a password reset. This can be used to send a different email when a user+    -- goes through the 'forgot password' flow as opposed to the 'account registration'+    -- flow.+    --+    -- Default: Will call 'sendVerifyEmail', resulting in the same email getting sent+    -- for both registrations and password resets.+    --+    -- @since 1.6.10+    sendForgotPasswordEmail :: Email -> VerKey -> VerUrl -> AuthHandler site ()+    sendForgotPasswordEmail = sendVerifyEmail++    -- | Get the verification key for the given email ID.+    --+    -- @since 1.1.0+    getVerifyKey :: AuthEmailId site -> AuthHandler site (Maybe VerKey)++    -- | Set the verification key for the given email ID.+    --+    -- @since 1.1.0+    setVerifyKey :: AuthEmailId site -> VerKey -> AuthHandler site ()++    -- | Hash and salt a password+    --+    -- Default: 'saltPass'.+    --+    -- @since 1.4.20+    hashAndSaltPassword :: Text -> AuthHandler site SaltedPass+    hashAndSaltPassword password = liftIO $ saltPass password++    -- | Verify a password matches the stored password for the given account.+    --+    -- Default: Fetch a password with 'getPassword' and match using 'Yesod.Auth.Util.PasswordStore.verifyPassword'.+    --+    -- @since 1.4.20+    verifyPassword :: Text -> SaltedPass -> AuthHandler site Bool+    verifyPassword plain salted = return $ isValidPass plain salted++    -- | Verify the email address on the given account.+    --+    -- __/Warning!/__ If you have persisted the @'AuthEmailId' site@+    -- somewhere, this method should delete that key, or make it unusable+    -- in some fashion. Otherwise, the same key can be used multiple times!+    --+    -- See <https://github.com/yesodweb/yesod/issues/1222>.+    --+    -- @since 1.1.0+    verifyAccount :: AuthEmailId site -> AuthHandler site (Maybe (AuthId site))++    -- | Get the salted password for the given account.+    --+    -- @since 1.1.0+    getPassword :: AuthId site -> AuthHandler site (Maybe SaltedPass)++    -- | Set the salted password for the given account.+    --+    -- @since 1.1.0+    setPassword :: AuthId site -> SaltedPass -> AuthHandler site ()++    -- | Get the credentials for the given @Identifier@, which may be either an+    -- email address or some other identification (e.g., username).+    --+    -- @since 1.2.0+    getEmailCreds :: Identifier -> AuthHandler site (Maybe (EmailCreds site))++    -- | Get the email address for the given email ID.+    --+    -- @since 1.1.0+    getEmail :: AuthEmailId site -> AuthHandler site (Maybe Email)++    -- | Generate a random alphanumeric string.+    --+    -- @since 1.1.0+    randomKey :: site -> IO VerKey+    randomKey _ = Nonce.nonce128urlT defaultNonceGen++    -- | Route to send user to after password has been set correctly.+    --+    -- @since 1.2.0+    afterPasswordRoute :: site -> Route site++    -- | Same as @afterPasswordRoute@ but allows you to run Handler code+    --+    -- If this function is overridden then @afterPasswordRoute@ is ignored.+    --+    -- @since 1.6.12.0+    afterPasswordRouteHandler :: AuthHandler site (Route site)+    afterPasswordRouteHandler = getYesod >>= pure . afterPasswordRoute++    -- | Route to send user to after verification with a password+    --+    -- @since 1.6.4+    afterVerificationWithPass :: site -> Route site+    afterVerificationWithPass = afterPasswordRoute++    -- | 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 -> AuthHandler site 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 -> AuthHandler site (Either Text ())+    checkPasswordSecurity _ x+        | TS.length x >= 3 = return $ Right ()+        | otherwise = return $ Left "Password must be at least three characters"++    -- | Response after sending a confirmation email.+    --+    -- @since 1.2.2+    confirmationEmailSentResponse :: Text -> AuthHandler site TypedContent+    confirmationEmailSentResponse identifier = do+        mr <- getMessageRender+        selectRep $ do+            provideJsonMessage (mr msg)+            provideRep $ authLayout $ do+              setTitleI Msg.ConfirmationEmailSentTitle+              [whamlet|<p>_{msg}|]+      where+        msg = Msg.ConfirmationEmailSent identifier++    -- | If a response is set, it will be used when an already-verified email+    -- tries to re-register. Otherwise, `confirmationEmailSentResponse` will be+    -- used.+    --+    -- @since 1.6.4+    emailPreviouslyRegisteredResponse :: MonadAuthHandler site m => Text -> Maybe (m TypedContent)+    emailPreviouslyRegisteredResponse _ = Nothing++    -- | Additional normalization of email addresses, besides standard canonicalization.+    --+    -- Default: Lower case the email address.+    --+    -- @since 1.2.3+    normalizeEmailAddress :: site -> Text -> Text+    normalizeEmailAddress _ = TS.toLower++    -- | Handler called to render the login page.+    -- The default works fine, but you may want to override it in+    -- order to have a different DOM.+    --+    -- Default: 'defaultEmailLoginHandler'.+    --+    -- @since 1.4.17+    emailLoginHandler :: (Route Auth -> Route site) -> WidgetFor site ()+    emailLoginHandler = defaultEmailLoginHandler+++    -- | Handler called to render the registration page.  The+    -- default works fine, but you may want to override it in+    -- order to have a different DOM.+    --+    -- Default: 'defaultRegisterHandler'.+    --+    -- @since: 1.2.6+    registerHandler :: AuthHandler site Html+    registerHandler = defaultRegisterHandler++    -- | Handler called to render the \"forgot password\" page.+    -- The default works fine, but you may want to override it in+    -- order to have a different DOM.+    --+    -- Default: 'defaultForgotPasswordHandler'.+    --+    -- @since: 1.2.6+    forgotPasswordHandler :: AuthHandler site Html+    forgotPasswordHandler = defaultForgotPasswordHandler++    -- | Handler called to render the \"set password\" page.  The+    -- default works fine, but you may want to override it in+    -- order to have a different DOM.+    --+    -- Default: 'defaultSetPasswordHandler'.+    --+    -- @since: 1.2.6+    setPasswordHandler ::+         Bool+         -- ^ Whether the old password is needed.  If @True@, a+         -- field for the old password should be presented.+         -- Otherwise, just two fields for the new password are+         -- needed.+      -> AuthHandler site TypedContent+    setPasswordHandler = defaultSetPasswordHandler+++    -- | Helper that controls what happens after a user registration+    -- request is submitted. This method can be overridden to completely+    -- customize what happens during the user registration process,+    -- such as for handling additional fields in the registration form.+    --+    -- The default implementation is in terms of 'defaultRegisterHelper'.+    --+    -- @since: 1.6.9+    registerHelper :: Route Auth+                      -- ^ Where to sent the user in the event+                      -- that registration fails+                   -> AuthHandler site TypedContent+    registerHelper = defaultRegisterHelper False False++    -- | Helper that controls what happens after a forgot password+    -- request is submitted. As with `registerHelper`, this method can+    -- be overridden to customize the behavior when a user attempts+    -- to recover their password.+    --+    -- The default implementation is in terms of 'defaultRegisterHelper'.+    --+    -- @since: 1.6.9+    passwordResetHelper :: Route Auth+                           -- ^ Where to sent the user in the event+                           -- that the password reset fails+                        -> AuthHandler site TypedContent+    passwordResetHelper = defaultRegisterHelper True True++authEmail :: (YesodAuthEmail m) => AuthPlugin m+authEmail =+    AuthPlugin "email" dispatch emailLoginHandler+  where+    dispatch :: YesodAuthEmail m => Text -> [Text] -> AuthHandler m TypedContent+    dispatch "GET" ["register"] = getRegisterR >>= sendResponse+    dispatch "POST" ["register"] = postRegisterR >>= sendResponse+    dispatch "GET" ["forgot-password"] = getForgotPasswordR >>= sendResponse+    dispatch "POST" ["forgot-password"] = postForgotPasswordR >>= sendResponse+    dispatch "GET" ["verify", eid, verkey] =+        case fromPathPiece eid of+            Nothing   -> notFound+            Just eid' -> getVerifyR eid' verkey False >>= sendResponse+    dispatch "GET" ["verify", eid, verkey, hasSetPass] =+        case fromPathPiece eid of+            Nothing -> notFound+            Just eid' -> getVerifyR eid' verkey (hasSetPass == verifyURLHasSetPassText) >>= sendResponse+    dispatch "POST" ["login"] = postLoginR >>= sendResponse+    dispatch "GET" ["set-password"] = getPasswordR >>= sendResponse+    dispatch "POST" ["set-password"] = postPasswordR >>= sendResponse+    dispatch _ _ = notFound++getRegisterR :: YesodAuthEmail master => AuthHandler master Html+getRegisterR = registerHandler++-- | Default implementation of 'emailLoginHandler'.+--+-- @since 1.4.17+defaultEmailLoginHandler+  :: YesodAuthEmail master+  => (Route Auth -> Route master)+  -> WidgetFor master ()+defaultEmailLoginHandler toParent = do+        (widget, enctype) <- generateFormPost loginForm++        [whamlet|+            <form method="post" action="@{toParent loginR}" enctype=#{enctype}>+                <div id="emailLoginForm">+                    ^{widget}+                    <div>+                        <button type=submit .btn .btn-success>+                            _{Msg.LoginViaEmail}+                        &nbsp;+                        <a href="@{toParent registerR}" .btn .btn-default>+                            _{Msg.RegisterLong}+        |]+  where+    loginForm extra = do++        emailMsg <- renderMessage' Msg.Email+        (emailRes, emailView) <- mreq emailField (emailSettings emailMsg) Nothing++        passwordMsg <- renderMessage' Msg.Password+        (passwordRes, passwordView) <- mreq passwordField (passwordSettings passwordMsg) Nothing++        let userRes = UserLoginForm <$> emailRes <*> passwordRes+        let widget = do+              [whamlet|+                  #{extra}+                  <div>+                      ^{fvInput emailView}+                  <div>+                      ^{fvInput passwordView}+              |]++        return (userRes, widget)+    emailSettings emailMsg = do+        FieldSettings {+            fsLabel = SomeMessage Msg.Email,+            fsTooltip = Nothing,+            fsId = Just "email",+            fsName = Just "email",+            fsAttrs = [("autofocus", ""), ("placeholder", emailMsg)]+        }+    passwordSettings passwordMsg =+         FieldSettings {+            fsLabel = SomeMessage Msg.Password,+            fsTooltip = Nothing,+            fsId = Just "password",+            fsName = Just "password",+            fsAttrs = [("placeholder", passwordMsg)]+        }+    renderMessage' msg = do+        langs <- languages+        master <- getYesod+        return $ renderAuthMessage master langs msg++-- | Default implementation of 'registerHandler'.+--+-- @since 1.2.6+defaultRegisterHandler :: YesodAuthEmail master => AuthHandler master Html+defaultRegisterHandler = do+    (widget, enctype) <- generateFormPost registrationForm+    toParentRoute <- getRouteToParent+    authLayout $ do+        setTitleI Msg.RegisterLong+        [whamlet|+            <p>_{Msg.EnterEmail}+            <form method="post" action="@{toParentRoute registerR}" enctype=#{enctype}>+                <div id="registerForm">+                    ^{widget}+                <button .btn>_{Msg.Register}+        |]+    where+        registrationForm extra = do+            let emailSettings = FieldSettings {+                fsLabel = SomeMessage Msg.Email,+                fsTooltip = Nothing,+                fsId = Just "email",+                fsName = Just "email",+                fsAttrs = [("autofocus", "")]+            }++            (emailRes, emailView) <- mreq emailField emailSettings Nothing++            let userRes = UserForm <$> emailRes+            let widget = do+                  [whamlet|+                      #{extra}+                      ^{fvLabel emailView}+                      ^{fvInput emailView}+                  |]++            return (userRes, widget)++parseRegister :: Value -> Parser (Text, Maybe Text)+parseRegister = withObject "email" (\obj -> do+                                      email <- obj .: "email"+                                      pass <- obj .:? "password"+                                      return (email, pass))++defaultRegisterHelper :: YesodAuthEmail master+                      => Bool -- ^ Allow lookup via username in addition to email+                      -> Bool -- ^ Set to `True` for forgot password flow, `False` for new account registration+                      -> Route Auth+                      -> AuthHandler master TypedContent+defaultRegisterHelper allowUsername forgotPassword dest = do+    y <- getYesod+    checkCsrfHeaderOrParam defaultCsrfHeaderName defaultCsrfParamName+    result <- runInputPostResult $ (,)+        <$> ireq textField "email"+        <*> iopt textField "password"++    creds <- case result of+                 FormSuccess (iden, pass) -> return $ Just (iden, pass)+                 _ -> do+                     (creds :: Result Value) <- parseCheckJsonBody+                     return $ case creds of+                                  Error _     -> Nothing+                                  Success val -> parseMaybe parseRegister val++    let eidentifier = case creds of+                          Nothing -> Left Msg.NoIdentifierProvided+                          Just (x, _)+                              | Just x' <- Text.Email.Validate.canonicalizeEmail (encodeUtf8 x) ->+                                         Right $ normalizeEmailAddress y $ decodeUtf8With lenientDecode x'+                              | allowUsername -> Right $ TS.strip x+                              | otherwise -> Left Msg.InvalidEmailAddress++    let mpass = case (forgotPassword, creds) of+                    (False, Just (_, mp)) -> mp+                    _                     -> Nothing++    case eidentifier of+      Left failMsg -> loginErrorMessageI dest failMsg+      Right identifier -> do+            mecreds <- getEmailCreds identifier+            registerCreds <-+                case mecreds of+                    Just (EmailCreds lid _ verStatus (Just key) email) -> return $ Just (lid, verStatus, key, email)+                    Just (EmailCreds lid _ verStatus Nothing email) -> do+                        key <- liftIO $ randomKey y+                        setVerifyKey lid key+                        return $ Just (lid, verStatus, key, email)+                    Nothing+                        | allowUsername -> return Nothing+                        | otherwise -> do+                            key <- liftIO $ randomKey y+                            lid <- case mpass of+                                Just pass -> do+                                    salted <- hashAndSaltPassword pass+                                    addUnverifiedWithPass identifier key salted+                                _ -> addUnverified identifier key+                            return $ Just (lid, False, key, identifier)+            case registerCreds of+                Nothing -> loginErrorMessageI dest (Msg.IdentifierNotFound identifier)+                Just regCreds@(_, False, _, _) -> sendConfirmationEmail regCreds+                Just regCreds@(_, True, _, _) -> do+                  if forgotPassword+                    then sendConfirmationEmail regCreds+                    else case emailPreviouslyRegisteredResponse identifier of+                      Just response -> response+                      Nothing       -> sendConfirmationEmail regCreds+              where sendConfirmationEmail (lid, _, verKey, email) = do+                      render <- getUrlRender+                      tp <- getRouteToParent+                      let verUrl = render $ tp $ verifyR (toPathPiece lid) verKey (isJust mpass)+                      if forgotPassword+                         then sendForgotPasswordEmail email verKey verUrl+                         else sendVerifyEmail email verKey verUrl+                      confirmationEmailSentResponse identifier+++postRegisterR :: YesodAuthEmail master => AuthHandler master TypedContent+postRegisterR = registerHelper registerR++getForgotPasswordR :: YesodAuthEmail master => AuthHandler master Html+getForgotPasswordR = forgotPasswordHandler++-- | Default implementation of 'forgotPasswordHandler'.+--+-- @since 1.2.6+defaultForgotPasswordHandler :: YesodAuthEmail master => AuthHandler master Html+defaultForgotPasswordHandler = do+    (widget, enctype) <- generateFormPost forgotPasswordForm+    toParent <- getRouteToParent+    authLayout $ do+        setTitleI Msg.PasswordResetTitle+        [whamlet|+            <p>_{Msg.PasswordResetPrompt}+            <form method=post action=@{toParent forgotPasswordR} enctype=#{enctype}>+                <div id="forgotPasswordForm">+                    ^{widget}+                    <button .btn>_{Msg.SendPasswordResetEmail}+        |]+  where+    forgotPasswordForm extra = do+        (emailRes, emailView) <- mreq emailField emailSettings Nothing++        let forgotPasswordRes = ForgotPasswordForm <$> emailRes+        let widget = do+              [whamlet|+                  #{extra}+                  ^{fvLabel emailView}+                  ^{fvInput emailView}+              |]+        return (forgotPasswordRes, widget)++    emailSettings =+        FieldSettings {+            fsLabel = SomeMessage Msg.ProvideIdentifier,+            fsTooltip = Nothing,+            fsId = Just "forgotPassword",+            fsName = Just "email",+            fsAttrs = [("autofocus", "")]+        }++postForgotPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent+postForgotPasswordR = passwordResetHelper forgotPasswordR++getVerifyR :: YesodAuthEmail site+           => AuthEmailId site+           -> Text+           -> Bool+           -> AuthHandler site TypedContent+getVerifyR lid key hasSetPass = do+    realKey <- getVerifyKey lid+    memail <- getEmail lid+    mr <- getMessageRender+    case (realKey == Just key, memail) of+        (True, Just email) -> do+            muid <- verifyAccount lid+            case muid of+                Nothing -> invalidKey mr+                Just uid -> do+                    setCreds False $ Creds "email-verify" email [("verifiedEmail", email)] -- FIXME uid?+                    setLoginLinkKey uid+                    let msgAv = if hasSetPass+                                  then Msg.EmailVerified+                                  else Msg.EmailVerifiedChangePass+                    selectRep $ do+                      provideRep $ do+                        addMessageI "success" msgAv+                        redirectRoute <- if hasSetPass+                            then do+                              y <- getYesod+                              return $ afterVerificationWithPass y+                            else do+                              tp <- getRouteToParent+                              return $ tp setpassR+                        fmap asHtml $ redirect redirectRoute+                      provideJsonMessage $ mr msgAv+        _ -> invalidKey mr+  where+    msgIk = Msg.InvalidKey+    invalidKey mr = messageJson401 (mr msgIk) $ authLayout $ do+        setTitleI msgIk+        [whamlet|+$newline never+<p>_{msgIk}+|]+++parseCreds :: Value -> Parser (Text, Text)+parseCreds = withObject "creds" (\obj -> do+                                   email' <- obj .: "email"+                                   pass <- obj .: "password"+                                   return (email', pass))+++postLoginR :: YesodAuthEmail master => AuthHandler master TypedContent+postLoginR = do+    result <- runInputPostResult $ (,)+        <$> ireq textField "email"+        <*> ireq textField "password"++    midentifier <- case result of+                     FormSuccess (iden, pass) -> return $ Just (iden, pass)+                     _ -> do+                       (creds :: Result Value) <- parseCheckJsonBody+                       case creds of+                         Error _     -> return Nothing+                         Success val -> return $ parseMaybe parseCreds val++    case midentifier of+      Nothing -> loginErrorMessageI LoginR Msg.NoIdentifierProvided+      Just (identifier, pass) -> do+          mecreds <- getEmailCreds identifier+          maid <-+              case ( mecreds >>= emailCredsAuthId+                   , emailCredsEmail <$> mecreds+                   , emailCredsStatus <$> mecreds+                   ) of+                (Just aid, Just email', Just True) -> do+                      mrealpass <- getPassword aid+                      case mrealpass of+                        Nothing -> return Nothing+                        Just realpass -> do+                            passValid <- verifyPassword pass realpass+                            return $ if passValid+                                    then Just email'+                                    else Nothing+                _ -> return Nothing+          let isEmail = Text.Email.Validate.isValid $ encodeUtf8 identifier+          case maid of+            Just email' ->+                setCredsRedirect $ Creds+                         (if isEmail then "email" else "username")+                         email'+                         [("verifiedEmail", email')]+            Nothing ->+                loginErrorMessageI LoginR $+                                   if isEmail+                                   then Msg.InvalidEmailPass+                                   else Msg.InvalidUsernamePass++getPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent+getPasswordR = do+    maid <- maybeAuthId+    case maid of+        Nothing -> loginErrorMessageI LoginR Msg.BadSetPass+        Just aid -> do+            needOld <- needOldPassword aid+            setPasswordHandler needOld++-- | Default implementation of 'setPasswordHandler'.+--+-- @since 1.2.6+defaultSetPasswordHandler :: YesodAuthEmail master => Bool -> AuthHandler master TypedContent+defaultSetPasswordHandler needOld = do+    messageRender <- getMessageRender+    toParent <- getRouteToParent+    selectRep $ do+        provideJsonMessage $ messageRender Msg.SetPass+        provideRep $ authLayout $ do+            (widget, enctype) <- generateFormPost setPasswordForm+            setTitleI Msg.SetPassTitle+            [whamlet|+                <h3>_{Msg.SetPass}+                <form method="post" action="@{toParent setpassR}" enctype=#{enctype}>+                    ^{widget}+            |]+  where+    setPasswordForm extra = do+        (currentPasswordRes, currentPasswordView) <- mreq passwordField currentPasswordSettings Nothing+        (newPasswordRes, newPasswordView) <- mreq passwordField newPasswordSettings Nothing+        (confirmPasswordRes, confirmPasswordView) <- mreq passwordField confirmPasswordSettings Nothing++        let passwordFormRes = PasswordForm <$> currentPasswordRes <*> newPasswordRes <*> confirmPasswordRes+        let widget = do+              [whamlet|+                  #{extra}+                  <table>+                      $if needOld+                          <tr>+                              <th>+                                  ^{fvLabel currentPasswordView}+                              <td>+                                  ^{fvInput currentPasswordView}+                      <tr>+                          <th>+                              ^{fvLabel newPasswordView}+                          <td>+                              ^{fvInput newPasswordView}+                      <tr>+                          <th>+                              ^{fvLabel confirmPasswordView}+                          <td>+                              ^{fvInput confirmPasswordView}+                      <tr>+                          <td colspan="2">+                              <input type=submit value=_{Msg.SetPassTitle}>+              |]++        return (passwordFormRes, widget)+    currentPasswordSettings =+         FieldSettings {+             fsLabel = SomeMessage Msg.CurrentPassword,+             fsTooltip = Nothing,+             fsId = Just "currentPassword",+             fsName = Just "current",+             fsAttrs = [("autofocus", "")]+         }+    newPasswordSettings =+        FieldSettings {+            fsLabel = SomeMessage Msg.NewPass,+            fsTooltip = Nothing,+            fsId = Just "newPassword",+            fsName = Just "new",+            fsAttrs = [("autofocus", ""), (":not", ""), ("needOld:autofocus", "")]+        }+    confirmPasswordSettings =+        FieldSettings {+            fsLabel = SomeMessage Msg.ConfirmPass,+            fsTooltip = Nothing,+            fsId = Just "confirmPassword",+            fsName = Just "confirm",+            fsAttrs = [("autofocus", "")]+        }++parsePassword :: Value -> Parser (Text, Text, Maybe Text)+parsePassword = withObject "password" (\obj -> do+                                         email' <- obj .: "new"+                                         pass <- obj .: "confirm"+                                         curr <- obj .:? "current"+                                         return (email', pass, curr))++postPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent+postPasswordR = do+    maid <- maybeAuthId+    (creds :: Result Value) <- parseCheckJsonBody+    let jcreds = case creds of+                   Error _     -> Nothing+                   Success val -> parseMaybe parsePassword val+    let doJsonParsing = isJust jcreds+    case maid of+        Nothing -> loginErrorMessageI LoginR Msg.BadSetPass+        Just aid -> do+            tm <- getRouteToParent+            needOld <- needOldPassword aid+            if not needOld then confirmPassword aid tm jcreds else do+                res <- runInputPostResult $ ireq textField "current"+                let fcurrent = case res of+                                 FormSuccess currentPass -> Just currentPass+                                 _                       -> Nothing+                let current = if doJsonParsing+                              then getThird jcreds+                              else fcurrent+                mrealpass <- getPassword aid+                case (mrealpass, current) of+                    (Nothing, _) ->+                        loginErrorMessage (tm setpassR) "You do not currently have a password set on your account"+                    (_, Nothing) ->+                        loginErrorMessageI LoginR Msg.BadSetPass+                    (Just realpass, Just current') -> do+                        passValid <- verifyPassword current' realpass+                        if passValid+                          then confirmPassword aid tm jcreds+                          else loginErrorMessage (tm setpassR) "Invalid current password, please try again"++  where+    msgOk = Msg.PassUpdated+    getThird (Just (_,_,t)) = t+    getThird Nothing        = Nothing+    getNewConfirm (Just (a,b,_)) = Just (a,b)+    getNewConfirm _              = Nothing+    confirmPassword aid tm jcreds = do+        res <- runInputPostResult $ (,)+            <$> ireq textField "new"+            <*> ireq textField "confirm"+        let creds = if (isJust jcreds)+                    then getNewConfirm jcreds+                    else case res of+                           FormSuccess res' -> Just res'+                           _                -> Nothing+        case creds of+          Nothing -> loginErrorMessageI setpassR Msg.PassMismatch+          Just (new, confirm) ->+              if new /= confirm+              then loginErrorMessageI setpassR Msg.PassMismatch+              else do+                isSecure <- checkPasswordSecurity aid new+                case isSecure of+                  Left e -> loginErrorMessage (tm setpassR) e+                  Right () -> do+                     salted <- hashAndSaltPassword new+                     setPassword aid salted+                     deleteSession loginLinkKey+                     addMessageI "success" msgOk+                     _ <- getYesod++                     mr <- getMessageRender+                     selectRep $ do+                         provideRep $ do+                            route <- afterPasswordRouteHandler+                            fmap asHtml $ redirect route+                         provideJsonMessage (mr msgOk)++saltLength :: Int+saltLength = 5++-- | Salt a password with a randomly generated salt.+saltPass :: Text -> IO Text+saltPass = fmap (decodeUtf8With lenientDecode)+         . flip PS.makePassword 16+         . encodeUtf8++saltPass' :: String -> String -> String+saltPass' salt pass =+    salt ++ T.unpack (TE.decodeUtf8 $ B16.encode $ convert (H.hash (TE.encodeUtf8 $ T.pack $ salt ++ pass) :: H.Digest H.MD5))++isValidPass :: Text -- ^ cleartext password+            -> SaltedPass -- ^ salted password+            -> Bool+isValidPass ct salted =+    PS.verifyPassword (encodeUtf8 ct) (encodeUtf8 salted) || isValidPass' ct salted++isValidPass' :: Text -- ^ cleartext password+            -> SaltedPass -- ^ salted password+            -> Bool+isValidPass' clear' salted' =+    let salt = take saltLength salted+     in salted == saltPass' salt clear+  where+    clear = TS.unpack clear'+    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 :: (MonadHandler m) => AuthId site -> m ()+setLoginLinkKey :: (MonadHandler m, YesodAuthEmail (HandlerSite m))+                => AuthId (HandlerSite m)+                -> m ()+setLoginLinkKey aid = do+    now <- liftIO getCurrentTime+    setSession loginLinkKey $ TS.pack $ show (toPathPiece aid, now)++-- See https://github.com/yesodweb/yesod/issues/1245 for discussion on this+-- use of unsafePerformIO.+defaultNonceGen :: Nonce.Generator+defaultNonceGen = unsafePerformIO (Nonce.new)+{-# NOINLINE defaultNonceGen #-}
− Yesod/Auth/GoogleEmail.hs
@@ -1,102 +0,0 @@-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE CPP #-}--- | Use an email address as an identifier via Google's OpenID login system.------ This backend will not use the OpenID identifier at all. It only uses OpenID--- as a login system. By using this plugin, you are trusting Google to validate--- an email address, and requiring users to have a Google account. On the plus--- side, you get to use email addresses as the identifier, many users have--- existing Google accounts, the login system has been long tested (as opposed--- to BrowserID), and it requires no credential managing or setup (as opposed--- to Email).-module Yesod.Auth.GoogleEmail-    ( authGoogleEmail-    , forwardUrl-    ) where--import Yesod.Auth-import qualified Web.Authenticate.OpenId as OpenId--import Yesod.Handler-import Yesod.Widget (whamlet)-import Yesod.Request-#if MIN_VERSION_blaze_html(0, 5, 0)-import Text.Blaze.Html (toHtml)-#else-import Text.Blaze (toHtml)-#endif-import Data.Text (Text)-import qualified Yesod.Auth.Message as Msg-import qualified Data.Text as T-import Control.Exception.Lifted (try, SomeException)--pid :: Text-pid = "googleemail"--forwardUrl :: AuthRoute-forwardUrl = PluginR pid ["forward"]--googleIdent :: Text-googleIdent = "https://www.google.com/accounts/o8/id"--authGoogleEmail :: YesodAuth m => AuthPlugin m-authGoogleEmail =-    AuthPlugin pid dispatch login-  where-    complete = PluginR pid ["complete"]-    login tm =-        [whamlet|-$newline never-<a href=@{tm forwardUrl}>_{Msg.LoginGoogle}-|]-    dispatch "GET" ["forward"] = do-        render <- getUrlRender-        toMaster <- getRouteToMaster-        let complete' = render $ toMaster complete-        master <- getYesod-        eres <- lift $ try $ OpenId.getForwardUrl googleIdent complete' Nothing-            [ ("openid.ax.type.email", "http://schema.openid.net/contact/email")-            , ("openid.ns.ax", "http://openid.net/srv/ax/1.0")-            , ("openid.ns.ax.required", "email")-            , ("openid.ax.mode", "fetch_request")-            , ("openid.ax.required", "email")-            , ("openid.ui.icon", "true")-            ] (authHttpManager master)-        either-          (\err -> do-                setMessage $ toHtml $ show (err :: SomeException)-                redirect $ toMaster LoginR-                )-          redirect-          eres-    dispatch "GET" ["complete", ""] = dispatch "GET" ["complete"] -- compatibility issues-    dispatch "GET" ["complete"] = do-        rr <- getRequest-        completeHelper $ reqGetParams rr-    dispatch "POST" ["complete", ""] = dispatch "POST" ["complete"] -- compatibility issues-    dispatch "POST" ["complete"] = do-        (posts, _) <- runRequestBody-        completeHelper posts-    dispatch _ _ = notFound--completeHelper :: YesodAuth m => [(Text, Text)] -> GHandler Auth m ()-completeHelper gets' = do-        master <- getYesod-        eres <- lift $ try $ OpenId.authenticateClaimed gets' (authHttpManager master)-        toMaster <- getRouteToMaster-        let onFailure err = do-            setMessage $ toHtml $ show (err :: SomeException)-            redirect $ toMaster LoginR-        let onSuccess oir = do-                let OpenId.Identifier ident = OpenId.oirOpLocal oir-                memail <- lookupGetParam "openid.ext1.value.email"-                case (memail, "https://www.google.com/accounts/o8/id" `T.isPrefixOf` ident) of-                    (Just email, True) -> setCreds True $ Creds pid email []-                    (_, False) -> do-                        setMessage "Only Google login is supported"-                        redirect $ toMaster LoginR-                    (Nothing, _) -> do-                        setMessage "No email address provided"-                        redirect $ toMaster LoginR-        either onFailure onSuccess eres
+ Yesod/Auth/GoogleEmail2.hs view
@@ -0,0 +1,616 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}++-- | Use an email address as an identifier via Google's login system.+--+-- Note that this is a replacement for "Yesod.Auth.GoogleEmail", which depends+-- on Google's now deprecated OpenID system. For more information, see+-- <https://developers.google.com/+/api/auth-migration>.+--+-- By using this plugin, you are trusting Google to validate an email address,+-- and requiring users to have a Google account. On the plus side, you get to+-- use email addresses as the identifier, many users have existing Google+-- accounts, the login system has been long tested (as opposed to BrowserID),+-- and it requires no credential managing or setup (as opposed to Email).+--+-- In order to use this plugin:+--+-- * Create an application on the Google Developer Console <https://console.developers.google.com/>+--+-- * Create OAuth credentials. The redirect URI will be <http://yourdomain/auth/page/googleemail2/complete>. (If you have your authentication subsite at a different root than \/auth\/, please adjust accordingly.)+--+-- * Enable the Google+ API.+--+-- @since 1.3.1+module Yesod.Auth.GoogleEmail2+    {-# DEPRECATED "Google+ is being shut down, please migrate to Google Sign-in https://pbrisbin.com/posts/googleemail2_deprecation/" #-}+    ( -- * Authentication handlers+      authGoogleEmail+    , authGoogleEmailSaveToken+    , forwardUrl+    -- * User authentication token+    , Token(..)+    , getUserAccessToken+    -- * Person+    , getPerson+    , Person(..)+    , Name(..)+    , Gender(..)+    , PersonImage(..)+    , resizePersonImage+    , RelationshipStatus(..)+    , PersonURI(..)+    , PersonURIType(..)+    , Organization(..)+    , OrganizationType(..)+    , Place(..)+    , Email(..)+    , EmailType(..)+    -- * Other functions+    , pid+    ) where++import           Yesod.Auth                  (Auth, AuthHandler,+                                              AuthPlugin (AuthPlugin),+                                              AuthRoute, Creds (Creds),+                                              Route (PluginR), YesodAuth,+                                              logoutDest, runHttpRequest,+                                              setCredsRedirect)+import qualified Yesod.Auth.Message          as Msg+import           Yesod.Core                  (HandlerSite, MonadHandler,+                                              TypedContent, addMessage,+                                              getRouteToParent, getUrlRender,+                                              getYesod, invalidArgs, liftIO,+                                              liftSubHandler, lookupGetParam,+                                              lookupSession, notFound, redirect,+                                              setSession, toHtml, whamlet, (.:))+++import           Blaze.ByteString.Builder    (fromByteString, toByteString)+import           Control.Arrow               (second)+import           Control.Monad               (unless, when)+import           Control.Monad.IO.Class      (MonadIO)+import qualified Crypto.Nonce                as Nonce+import           Data.Aeson                  ((.:?))+import qualified Data.Aeson                  as A+#if MIN_VERSION_aeson(1,0,0)+import qualified Data.Aeson.Text             as A+#else+import qualified Data.Aeson.Encode           as A+#endif+import           Data.Aeson.Parser           (json')+import           Data.Aeson.Types            (FromJSON (parseJSON), parseEither,+                                              parseMaybe, withObject, withText)+import           Data.Conduit+import           Data.Conduit.Attoparsec     (sinkParser)+import           Data.Maybe                  (fromMaybe)+import           Data.Text                   (Text)+import qualified Data.Text                   as T+import           Data.Text.Encoding          (decodeUtf8, encodeUtf8)+import qualified Data.Text.Lazy              as TL+import qualified Data.Text.Lazy.Builder      as TL+import           Network.HTTP.Client         (Manager, requestHeaders,+                                              responseBody, urlEncodedBody)+import qualified Network.HTTP.Client         as HTTP+import           Network.HTTP.Client.Conduit (Request, bodyReaderSource)+import           Network.HTTP.Conduit        (http)+import           Network.HTTP.Types          (renderQueryText)+import           System.IO.Unsafe            (unsafePerformIO)++#if MIN_VERSION_aeson(2, 0, 0)+import qualified Data.Aeson.Key+import qualified Data.Aeson.KeyMap+#else+import qualified Data.HashMap.Strict         as M+#endif+++-- | Plugin identifier. This is used to identify the plugin used for+-- authentication. The 'credsPlugin' will contain this value when this+-- plugin is used for authentication.+-- @since 1.4.17+pid :: Text+pid = "googleemail2"++forwardUrl :: AuthRoute+forwardUrl = PluginR pid ["forward"]++csrfKey :: Text+csrfKey = "_GOOGLE_CSRF_TOKEN"++getCsrfToken :: MonadHandler m => m (Maybe Text)+getCsrfToken = lookupSession csrfKey++accessTokenKey :: Text+accessTokenKey = "_GOOGLE_ACCESS_TOKEN"++-- | Get user's access token from the session. Returns Nothing if it's not found+--   (probably because the user is not logged in via 'Yesod.Auth.GoogleEmail2'+--   or you are not using 'authGoogleEmailSaveToken')+getUserAccessToken :: MonadHandler m => m (Maybe Token)+getUserAccessToken = fmap (\t -> Token t "Bearer") <$> lookupSession accessTokenKey++getCreateCsrfToken :: MonadHandler m => m Text+getCreateCsrfToken = do+    mtoken <- getCsrfToken+    case mtoken of+        Just token -> return token+        Nothing -> do+            token <- Nonce.nonce128urlT defaultNonceGen+            setSession csrfKey token+            return token++authGoogleEmail :: YesodAuth m+                => Text -- ^ client ID+                -> Text -- ^ client secret+                -> AuthPlugin m+authGoogleEmail = authPlugin False++-- | An alternative version which stores user access token in the session+--   variable. Use it if you want to request user's profile from your app.+--+-- @since 1.4.3+authGoogleEmailSaveToken :: YesodAuth m+                         => Text -- ^ client ID+                         -> Text -- ^ client secret+                         -> AuthPlugin m+authGoogleEmailSaveToken = authPlugin True++authPlugin :: YesodAuth m+           => Bool -- ^ if the token should be stored+           -> Text -- ^ client ID+           -> Text -- ^ client secret+           -> AuthPlugin m+authPlugin storeToken clientID clientSecret =+    AuthPlugin pid dispatch login+  where+    complete = PluginR pid ["complete"]++    getDest :: MonadHandler m+            => (Route Auth -> Route (HandlerSite m))+            -> m Text+    getDest tm = do+        csrf <- getCreateCsrfToken+        render <- getUrlRender+        let qs = map (second Just)+                [ ("scope", "email profile")+                , ("state", csrf)+                , ("redirect_uri", render $ tm complete)+                , ("response_type", "code")+                , ("client_id", clientID)+                , ("access_type", "offline")+                ]+        return $ decodeUtf8+               $ toByteString+               $ fromByteString "https://accounts.google.com/o/oauth2/auth"+                    `mappend` renderQueryText True qs++    login tm = do+        [whamlet|<a href=@{tm forwardUrl}>_{Msg.LoginGoogle}|]++    dispatch :: YesodAuth site+             => Text+             -> [Text]+             -> AuthHandler site TypedContent+    dispatch "GET" ["forward"] = do+        tm <- getRouteToParent+        getDest tm >>= redirect++    dispatch "GET" ["complete"] = do+        mstate <- lookupGetParam "state"+        case mstate of+            Nothing -> invalidArgs ["CSRF state from Google is missing"]+            Just state -> do+                mtoken <- getCsrfToken+                unless (Just state == mtoken) $ invalidArgs ["Invalid CSRF token from Google"]+        mcode <- lookupGetParam "code"+        code <-+            case mcode of+                Nothing -> do+                    merr <- lookupGetParam "error"+                    case merr of+                        Nothing -> invalidArgs ["Missing code paramter"]+                        Just err -> do+                            master <- getYesod+                            let msg =+                                    case err of+                                        "access_denied" -> "Access denied"+                                        _ -> "Unknown error occurred: " `T.append` err+                            addMessage "error" $ toHtml msg+                            redirect $ logoutDest master+                Just c -> return c++        render <- getUrlRender+        tm <- getRouteToParent++        req' <- liftIO $+            HTTP.parseUrlThrow+            "https://accounts.google.com/o/oauth2/token" -- FIXME don't hardcode, use: https://accounts.google.com/.well-known/openid-configuration+        let req =+                urlEncodedBody+                    [ ("code", encodeUtf8 code)+                    , ("client_id", encodeUtf8 clientID)+                    , ("client_secret", encodeUtf8 clientSecret)+                    , ("redirect_uri", encodeUtf8 $ render $ tm complete)+                    , ("grant_type", "authorization_code")+                    ]+                    req'+                        { requestHeaders = []+                        }+        value <- makeHttpRequest req+        token@(Token accessToken' tokenType') <-+            case parseEither parseJSON value of+                Left e  -> error e+                Right t -> return t++        unless (tokenType' == "Bearer") $ error $ "Unknown token type: " ++ show tokenType'++        -- User's access token is saved for further access to API+        when storeToken $ setSession accessTokenKey accessToken'++        personValReq <- personValueRequest token+        personValue <- makeHttpRequest personValReq++        person <- case parseEither parseJSON personValue of+                Left e  -> error e+                Right x -> return x++        email <-+            case map emailValue $ filter (\e -> emailType e == EmailAccount) $ personEmails person of+                [e] -> return e+                []  -> error "No account email"+                x   -> error $ "Too many account emails: " ++ show x+        setCredsRedirect $ Creds pid email $ allPersonInfo personValue++    dispatch _ _ = notFound++makeHttpRequest :: Request -> AuthHandler site A.Value+makeHttpRequest req =+    liftSubHandler $ runHttpRequest req $ \res ->+    runConduit $ bodyReaderSource (responseBody res) .| sinkParser json'++-- | Allows to fetch information about a user from Google's API.+--   In case of parsing error returns 'Nothing'.+--   Will throw 'HttpException' in case of network problems or error response code.+--+-- @since 1.4.3+getPerson :: MonadHandler m => Manager -> Token -> m (Maybe Person)+getPerson manager token = liftSubHandler $ parseMaybe parseJSON <$> (do+    req <- personValueRequest token+    res <- http req manager+    runConduit $ responseBody res .| sinkParser json'+  )++personValueRequest :: MonadIO m => Token -> m Request+personValueRequest token = do+    req2' <- liftIO+           $ HTTP.parseUrlThrow "https://www.googleapis.com/plus/v1/people/me"+    return req2'+            { requestHeaders =+                [ ("Authorization", encodeUtf8 $ "Bearer " `mappend` accessToken token)+                ]+            }++--------------------------------------------------------------------------------+-- | An authentication token which was acquired from OAuth callback.+--   The token gets saved into the session storage only if you use+--   'authGoogleEmailSaveToken'.+--   You can acquire saved token with 'getUserAccessToken'.+--+-- @since 1.4.3+data Token = Token { accessToken :: Text+                   , tokenType   :: Text+                   } deriving (Show, Eq)++instance FromJSON Token where+    parseJSON = withObject "Tokens" $ \o ->+        Token+            <$> o .: "access_token"+            <*> o .: "token_type"++--------------------------------------------------------------------------------+-- | Gender of the person+--+-- @since 1.4.3+data Gender = Male | Female | OtherGender deriving (Show, Eq)++instance FromJSON Gender where+    parseJSON = withText "Gender" $ \t -> return $ case t of+                                                "male"   -> Male+                                                "female" -> Female+                                                _        -> OtherGender++--------------------------------------------------------------------------------+-- | URIs specified in the person's profile+--+-- @since 1.4.3+data PersonURI =+    PersonURI { uriLabel :: Maybe Text+              , uriValue :: Maybe Text+              , uriType  :: Maybe PersonURIType+              } deriving (Show, Eq)++instance FromJSON PersonURI where+    parseJSON = withObject "PersonURI" $ \o -> PersonURI <$> o .:? "label"+                                                         <*> o .:? "value"+                                                         <*> o .:? "type"++--------------------------------------------------------------------------------+-- | The type of URI+--+-- @since 1.4.3+data PersonURIType = OtherProfile       -- ^ URI for another profile+                   | Contributor        -- ^ URI to a site for which this person is a contributor+                   | Website            -- ^ URI for this Google+ Page's primary website+                   | OtherURI           -- ^ Other URL+                   | PersonURIType Text -- ^ Something else+                   deriving (Show, Eq)++instance FromJSON PersonURIType where+    parseJSON = withText "PersonURIType" $ \t -> return $ case t of+            "otherProfile" -> OtherProfile+            "contributor"  -> Contributor+            "website"      -> Website+            "other"        -> OtherURI+            _              -> PersonURIType t++--------------------------------------------------------------------------------+-- | Current or past organizations with which this person is associated+--+-- @since 1.4.3+data Organization =+    Organization { orgName      :: Maybe Text+                   -- ^ The person's job title or role within the organization+                 , orgTitle     :: Maybe Text+                 , orgType      :: Maybe OrganizationType+                   -- ^ The date that the person joined this organization.+                 , orgStartDate :: Maybe Text+                   -- ^ The date that the person left this organization.+                 , orgEndDate   :: Maybe Text+                   -- ^ If @True@, indicates this organization is the person's+                   -- ^ primary one, which is typically interpreted as the current one.+                 , orgPrimary   :: Maybe Bool+                 } deriving (Show, Eq)++instance FromJSON Organization where+    parseJSON = withObject "Organization" $ \o ->+        Organization <$> o .:? "name"+                     <*> o .:? "title"+                     <*> o .:? "type"+                     <*> o .:? "startDate"+                     <*> o .:? "endDate"+                     <*> o .:? "primary"++--------------------------------------------------------------------------------+-- | The type of an organization+--+-- @since 1.4.3+data OrganizationType = Work+                      | School+                      | OrganizationType Text -- ^ Something else+                      deriving (Show, Eq)+instance FromJSON OrganizationType where+    parseJSON = withText "OrganizationType" $ \t -> return $ case t of+                                                "work"   -> Work+                                                "school" -> School+                                                _        -> OrganizationType t++--------------------------------------------------------------------------------+-- | A place where the person has lived or is living at the moment.+--+-- @since 1.4.3+data Place =+    Place { -- | A place where this person has lived. For example: "Seattle, WA", "Near Toronto".+            placeValue   :: Maybe Text+            -- | If @True@, this place of residence is this person's primary residence.+          , placePrimary :: Maybe Bool+          } deriving (Show, Eq)++instance FromJSON Place where+    parseJSON = withObject "Place" $ \o -> Place <$> (o .:? "value") <*> (o .:? "primary")++--------------------------------------------------------------------------------+-- | Individual components of a name+--+-- @since 1.4.3+data Name =+    Name { -- | The full name of this person, including middle names, suffixes, etc+           nameFormatted       :: Maybe Text+           -- | The family name (last name) of this person+         , nameFamily          :: Maybe Text+           -- | The given name (first name) of this person+         , nameGiven           :: Maybe Text+           -- | The middle name of this person.+         , nameMiddle          :: Maybe Text+           -- | The honorific prefixes (such as "Dr." or "Mrs.") for this person+         , nameHonorificPrefix :: Maybe Text+           -- | The honorific suffixes (such as "Jr.") for this person+         , nameHonorificSuffix :: Maybe Text+         } deriving (Show, Eq)++instance FromJSON Name where+    parseJSON = withObject "Name" $ \o -> Name <$> o .:? "formatted"+                                               <*> o .:? "familyName"+                                               <*> o .:? "givenName"+                                               <*> o .:? "middleName"+                                               <*> o .:? "honorificPrefix"+                                               <*> o .:? "honorificSuffix"++--------------------------------------------------------------------------------+-- | The person's relationship status.+--+-- @since 1.4.3+data RelationshipStatus = Single              -- ^ Person is single+                        | InRelationship      -- ^ Person is in a relationship+                        | Engaged             -- ^ Person is engaged+                        | Married             -- ^ Person is married+                        | Complicated         -- ^ The relationship is complicated+                        | OpenRelationship    -- ^ Person is in an open relationship+                        | Widowed             -- ^ Person is widowed+                        | DomesticPartnership -- ^ Person is in a domestic partnership+                        | CivilUnion          -- ^ Person is in a civil union+                        | RelationshipStatus Text -- ^ Something else+                        deriving (Show, Eq)++instance FromJSON RelationshipStatus where+    parseJSON = withText "RelationshipStatus" $ \t -> return $ case t of+                  "single"                  -> Single+                  "in_a_relationship"       -> InRelationship+                  "engaged"                 -> Engaged+                  "married"                 -> Married+                  "its_complicated"         -> Complicated+                  "open_relationship"       -> OpenRelationship+                  "widowed"                 -> Widowed+                  "in_domestic_partnership" -> DomesticPartnership+                  "in_civil_union"          -> CivilUnion+                  _                         -> RelationshipStatus t++--------------------------------------------------------------------------------+-- | The URI of the person's profile photo.+--+-- @since 1.4.3+newtype PersonImage = PersonImage { imageUri :: Text } deriving (Show, Eq)++instance FromJSON PersonImage where+    parseJSON = withObject "PersonImage" $ \o -> PersonImage <$> o .: "url"++-- | @resizePersonImage img 30@ would set query part to @?sz=30@ which would resize+--   the image under the URI. If for some reason you need to modify the query+--   part, you should do it after resizing.+--+-- @since 1.4.3+resizePersonImage :: PersonImage -> Int -> PersonImage+resizePersonImage (PersonImage uri) size =+    PersonImage $ uri `mappend` "?sz=" `mappend` T.pack (show size)++--------------------------------------------------------------------------------+-- | Information about the user+--   Full description of the resource https://developers.google.com/+/api/latest/people+--+-- @since 1.4.3+data Person = Person+    { personId                 :: Text+      -- | The name of this person, which is suitable for display+    , personDisplayName        :: Maybe Text+    , personName               :: Maybe Name+    , personNickname           :: Maybe Text+    , personBirthday           :: Maybe Text -- ^ Birthday formatted as YYYY-MM-DD+    , personGender             :: Maybe Gender+    , personProfileUri         :: Maybe Text -- ^ The URI of this person's profile+    , personImage              :: Maybe PersonImage+    , personAboutMe            :: Maybe Text -- ^ A short biography for this person+    , personRelationshipStatus :: Maybe RelationshipStatus+    , personUris               :: [PersonURI]+    , personOrganizations      :: [Organization]+    , personPlacesLived        :: [Place]+    -- | The brief description of this person+    , personTagline            :: Maybe Text+    -- | Whether this user has signed up for Google++    , personIsPlusUser         :: Maybe Bool+    -- | The "bragging rights" line of this person+    , personBraggingRights     :: Maybe Text+    -- | if a Google+ page, the number of people who have +1'd this page+    , personPlusOneCount       :: Maybe Int+    -- | For followers who are visible, the number of people who have added+    --   this person or page to a circle.+    , personCircledByCount     :: Maybe Int+    -- | Whether the person or Google+ Page has been verified. This is used only+    --   for pages with a higher risk of being impersonated or similar. This+    --   flag will not be present on most profiles.+    , personVerified           :: Maybe Bool+    -- | The user's preferred language for rendering.+    , personLanguage           :: Maybe Text+    , personEmails             :: [Email]+    , personDomain             :: Maybe Text+    , personOccupation         :: Maybe Text -- ^ The occupation of this person+    , personSkills             :: Maybe Text -- ^ The person's skills+    } deriving (Show, Eq)+++instance FromJSON Person where+    parseJSON = withObject "Person" $ \o ->+        Person <$> o .:  "id"+               <*> o .:  "displayName"+               <*> o .:? "name"+               <*> o .:? "nickname"+               <*> o .:? "birthday"+               <*> o .:? "gender"+               <*> (o .:? "url")+               <*> o .:? "image"+               <*> o .:? "aboutMe"+               <*> o .:? "relationshipStatus"+               <*> ((fromMaybe []) <$> (o .:? "urls"))+               <*> ((fromMaybe []) <$> (o .:? "organizations"))+               <*> ((fromMaybe []) <$> (o .:? "placesLived"))+               <*> o .:? "tagline"+               <*> o .:? "isPlusUser"+               <*> o .:? "braggingRights"+               <*> o .:? "plusOneCount"+               <*> o .:? "circledByCount"+               <*> o .:? "verified"+               <*> o .:? "language"+               <*> ((fromMaybe []) <$> (o .:? "emails"))+               <*> o .:? "domain"+               <*> o .:? "occupation"+               <*> o .:? "skills"++--------------------------------------------------------------------------------+-- | Person's email+--+-- @since 1.4.3+data Email = Email+    { emailValue :: Text+    , emailType  :: EmailType+    }+    deriving (Show, Eq)++instance FromJSON Email where+    parseJSON = withObject "Email" $ \o -> Email+        <$> o .: "value"+        <*> o .: "type"++--------------------------------------------------------------------------------+-- | Type of email+--+-- @since 1.4.3+data EmailType = EmailAccount   -- ^ Google account email address+               | EmailHome      -- ^ Home email address+               | EmailWork      -- ^ Work email adress+               | EmailOther     -- ^ Other email address+               | EmailType Text -- ^ Something else+               deriving (Show, Eq)++instance FromJSON EmailType where+    parseJSON = withText "EmailType" $ \t -> return $ case t of+        "account" -> EmailAccount+        "home"    -> EmailHome+        "work"    -> EmailWork+        "other"   -> EmailOther+        _         -> EmailType t++allPersonInfo :: A.Value -> [(Text, Text)]+allPersonInfo (A.Object o) = map enc $ mapToList o+  where+    enc (key, A.String s) = (keyToText key, s)+    enc (key, v) = (keyToText key, TL.toStrict $ TL.toLazyText $ A.encodeToTextBuilder v)++#if MIN_VERSION_aeson(2, 0, 0)+    keyToText = Data.Aeson.Key.toText+    mapToList = Data.Aeson.KeyMap.toList+#else+    keyToText = id+    mapToList = M.toList+#endif++allPersonInfo _ = []+++-- See https://github.com/yesodweb/yesod/issues/1245 for discussion on this+-- use of unsafePerformIO.+defaultNonceGen :: Nonce.Generator+defaultNonceGen = unsafePerformIO (Nonce.new)+{-# NOINLINE defaultNonceGen #-}
+ Yesod/Auth/Hardcoded.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}++{-|+Module      : Yesod.Auth.Hardcoded+Description : Very simple auth plugin for hardcoded auth pairs.+Copyright   : (c) Arthur Fayzrakhmanov, 2015+License     : MIT+Maintainer  : heraldhoi@gmail.com+Stability   : experimental++Sometimes you may want to have some hardcoded set of users (e.g. site managers)+that allowed to log in and visit some specific sections of your website without+ability to register new managers.  This simple plugin is designed exactly for+this purpose.++Here is a quick usage example.++== Define hardcoded users representation++Let's assume, that we want to have some hardcoded managers with normal site+users.  Let's define hardcoded user representation:++@+data SiteManager = SiteManager+  { manUserName :: Text+  , manPassWord :: Text }+  deriving Show++siteManagers :: [SiteManager]+siteManagers = [SiteManager "content editor" "top secret"]+@+++== Describe 'YesodAuth' instance++Now we need to have some convenient 'AuthId' type representing both+cases:++@+instance YesodAuth App where+  type AuthId App = Either UserId Text+@++Here, right @Text@ value will present hardcoded user name (which obviously must+be unique).++'AuthId' must have an instance of 'PathPiece' class, this is needed to store+user identifier in session (this happens in 'setCreds' and 'setCredsRedirect'+actions) and to read that identifier from session (this happens in+`defaultMaybeAuthId` action).  So we have to define it:++@+import Text.Read (readMaybe)++instance PathPiece (Either UserId Text) where+  fromPathPiece = readMaybe . unpack+  toPathPiece = pack . show+@++Quiet simple so far.  Now let's add plugin to 'authPlugins' list, and define+'authenticate' method, it should return user identifier for given credentials,+for normal users it is usually persistent key, for hardcoded users we will+return user name again.++@+instance YesodAuth App where+  -- ..+  authPlugins _ = [authHardcoded]++  authenticate Creds{..} =+    return+      (case credsPlugin of+         "hardcoded" ->+           case lookupUser credsIdent of+             Nothing -> UserError InvalidLogin+             Just m  -> Authenticated (Right (manUserName m)))+@++Here @lookupUser@ is just a helper function to lookup hardcoded users by name:++@+lookupUser :: Text -> Maybe SiteManager+lookupUser username = find (\\m -> manUserName m == username) siteManagers+@+++== Describe an 'YesodAuthPersist' instance++Now we need to manually define 'YesodAuthPersist' instance.++> instance YesodAuthPersist App where+>   type AuthEntity App = Either User SiteManager+>+>   getAuthEntity (Left uid) =+>     do x <- runDB (get uid)+>        return (Left <$> x)+>   getAuthEntity (Right username) = return (Right <$> lookupUser username)+++== Define 'YesodAuthHardcoded' instance++Finally, let's define an plugin instance++@+instance YesodAuthHardcoded App where+  validatePassword u = return . validPassword u+  doesUserNameExist  = return . isJust . lookupUser++validPassword :: Text -> Text -> Bool+validPassword u p =+  case find (\\m -> manUserName m == u && manPassWord m == p) siteManagers of+    Just _ -> True+    _      -> False+@+++== Conclusion++Now we can use 'maybeAuthId', 'maybeAuthPair', 'requireAuthId', and+'requireAuthPair', moreover, the returned value makes possible to distinguish+normal users and site managers.+-}+module Yesod.Auth.Hardcoded+  ( YesodAuthHardcoded(..)+  , authHardcoded+  , loginR )+  where++import           Yesod.Auth          (AuthHandler, AuthPlugin (..), AuthRoute,+                                      Creds (..), Route (..), YesodAuth,+                                      loginErrorMessageI, setCredsRedirect)+import qualified Yesod.Auth.Message  as Msg+import           Yesod.Core+import           Yesod.Form          (ireq, runInputPost, textField)+import           Data.Text           (Text)+++loginR :: AuthRoute+loginR = PluginR "hardcoded" ["login"]++class (YesodAuth site) => YesodAuthHardcoded site where++  -- | Check whether given user name exists among hardcoded names.+  doesUserNameExist :: Text -> AuthHandler site Bool++  -- | Validate given user name with given password.+  validatePassword :: Text -> Text -> AuthHandler site Bool+++authHardcoded :: YesodAuthHardcoded m => AuthPlugin m+authHardcoded =+  AuthPlugin "hardcoded" dispatch loginWidget+  where+    dispatch :: YesodAuthHardcoded m => Text -> [Text] -> AuthHandler m TypedContent+    dispatch "POST" ["login"] = postLoginR >>= sendResponse+    dispatch _ _              = notFound+    loginWidget toMaster = do+      request <- getRequest+      [whamlet|+        $newline never+        <form method="post" action="@{toMaster loginR}">+          $maybe t <- reqToken request+            <input type=hidden name=#{defaultCsrfParamName} value=#{t}>+          <table>+            <tr>+              <th>_{Msg.UserName}+              <td>+                 <input type="text" name="username" required>+            <tr>+              <th>_{Msg.Password}+              <td>+                 <input type="password" name="password" required>+            <tr>+              <td colspan="2">+                 <button type="submit" .btn .btn-success>_{Msg.LoginTitle}+        |]+++postLoginR :: YesodAuthHardcoded site+           => AuthHandler site TypedContent+postLoginR =+  do (username, password) <- runInputPost+       ((,) <$> ireq textField "username"+            <*> ireq textField "password")+     isValid <- validatePassword username password+     if isValid+        then setCredsRedirect (Creds "hardcoded" username [])+        else do isExists <- doesUserNameExist username+                loginErrorMessageI LoginR+                                   (if isExists+                                       then Msg.InvalidUsernamePass+                                       else Msg.IdentifierNotFound username)
− Yesod/Auth/HashDB.hs
@@ -1,316 +0,0 @@-{-# LANGUAGE QuasiQuotes                #-}-{-# LANGUAGE FlexibleContexts           #-}-{-# LANGUAGE TypeFamilies               #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TemplateHaskell            #-}-{-# LANGUAGE OverloadedStrings          #-}-{-# LANGUAGE GADTs                      #-}-{-# LANGUAGE CPP                        #-}----------------------------------------------------------------------------------- |--- Module      :  Yesod.Auth.HashDB--- Copyright   :  (c) Patrick Brisbin 2010 --- License     :  as-is------ Maintainer  :  pbrisbin@gmail.com--- Stability   :  Stable--- Portability :  Portable------ A yesod-auth AuthPlugin designed to look users up in Persist where--- their user id's and a salted SHA1 hash of their password is stored.------ Example usage:------ > -- import the function--- > import Auth.HashDB--- >--- > -- make sure you have an auth route--- > mkYesodData "MyApp" [$parseRoutes|--- > / RootR GET--- > /auth AuthR Auth getAuth--- > |]--- >--- >--- > -- make your app an instance of YesodAuth using this plugin--- > instance YesodAuth MyApp where--- >    type AuthId MyApp = UserId--- >--- >    loginDest _  = RootR--- >    logoutDest _ = RootR--- >    getAuthId    = getAuthIdHashDB AuthR (Just . UniqueUser)--- >    authPlugins  = [authHashDB (Just . UniqueUser)]--- >--- >--- > -- include the migration function in site startup--- > withServer :: (Application -> IO a) -> IO a--- > withServer f = withConnectionPool $ \p -> do--- >     runSqlPool (runMigration migrateUsers) p--- >     let h = DevSite p------ Note that function which converts username to unique identifier must be same.------ Your app must be an instance of YesodPersist. and the username,--- salt and hashed-passwords should be added to the database.------ > echo -n 'MySaltMyPassword' | sha1sum------ can be used to get the hash from the commandline.------------------------------------------------------------------------------------module Yesod.Auth.HashDB-    ( HashDBUser(..)-    , Unique (..)-    , setPassword-      -- * Authentification-    , validateUser-    , authHashDB-    , getAuthIdHashDB-      -- * Predefined data type-    , User-    , UserGeneric (..)-    , UserId-    , EntityField (..)-    , migrateUsers-    ) where--import Yesod.Persist-import Yesod.Handler-import Yesod.Form-import Yesod.Auth-import Yesod.Widget (toWidget)-import Text.Hamlet (hamlet)--import Control.Applicative         ((<$>), (<*>))-import Control.Monad               (replicateM,liftM)-import Control.Monad.IO.Class      (MonadIO, liftIO)--import qualified Data.ByteString.Lazy.Char8 as BS (pack)-import Data.Digest.Pure.SHA        (sha1, showDigest)-import Data.Text                   (Text, pack, unpack, append)-import Data.Maybe                  (fromMaybe)-import System.Random               (randomRIO)---- | Interface for data type which holds user info. It's just a---   collection of getters and setters-class HashDBUser user where-  -- | Retrieve password hash from user data-  userPasswordHash :: user -> Maybe Text-  -- | Retrieve salt for password-  userPasswordSalt :: user -> Maybe Text--  -- | Deprecated for the better named setSaltAndPasswordHash -  setUserHashAndSalt :: Text    -- ^ Salt-                     -> Text    -- ^ Password hash-                     -> user -> user-  setUserHashAndSalt = setSaltAndPasswordHash--  -- | a callback for setPassword-  setSaltAndPasswordHash :: Text    -- ^ Salt-                     -> Text    -- ^ Password hash-                     -> user -> user-  setSaltAndPasswordHash = setUserHashAndSalt---- | Generate random salt. Length of 8 is chosen arbitrarily-randomSalt :: MonadIO m => m Text-randomSalt = pack `liftM` liftIO (replicateM 8 (randomRIO ('0','z')))---- | Calculate salted hash using SHA1.-saltedHash :: Text              -- ^ Salt-           -> Text              -- ^ Password-           -> Text-saltedHash salt = -  pack . showDigest . sha1 . BS.pack . unpack . append salt---- | Set password for user. This function should be used for setting---   passwords. It generates random salt and calculates proper hashes.-setPassword :: (MonadIO m, HashDBUser user) => Text -> user -> m user-setPassword pwd u = do salt <- randomSalt-                       return $ setSaltAndPasswordHash salt (saltedHash salt pwd) u---------------------------------------------------------------------- Authentification--------------------------------------------------------------------- | Given a user ID and password in plaintext, validate them against---   the database values.-validateUser :: ( YesodPersist yesod-#if MIN_VERSION_persistent(1, 1, 0)-                , b ~ YesodPersistBackend yesod-                , PersistMonadBackend (b (GHandler sub yesod)) ~ PersistEntityBackend user-                , PersistUnique (b (GHandler sub yesod))-#else-                , b ~ YesodPersistBackend yesod-                , b ~ PersistEntityBackend user-                , PersistStore b (GHandler sub yesod)-                , PersistUnique b (GHandler sub yesod)-#endif-                , PersistEntity user-                , HashDBUser    user-                ) => -#if MIN_VERSION_persistent(1, 1, 0)-                Unique user     -- ^ User unique identifier-#else-                Unique user b   -- ^ User unique identifier-#endif-             -> Text            -- ^ Password in plaint-text-             -> GHandler sub yesod Bool-validateUser userID passwd = do-  -- Checks that hash and password match-  let validate u = do hash <- userPasswordHash u-                      salt <- userPasswordSalt u-                      return $ hash == saltedHash salt passwd-  -- Get user data-  user <- runDB $ getBy userID-  return $ fromMaybe False $ validate . entityVal =<< user---login :: AuthRoute-login = PluginR "hashdb" ["login"]----- | Handle the login form. First parameter is function which maps---   username (whatever it might be) to unique user ID.-postLoginR :: ( YesodAuth y, YesodPersist y-              , HashDBUser user, PersistEntity user-#if MIN_VERSION_persistent(1, 1, 0)-              , b ~ YesodPersistBackend y-              , PersistMonadBackend (b (GHandler Auth y)) ~ PersistEntityBackend user-              , PersistUnique (b (GHandler Auth y))-#else-              , b ~ YesodPersistBackend y-              , b ~ PersistEntityBackend user-              , PersistStore b (GHandler Auth y)-              , PersistUnique b (GHandler Auth y)-#endif-              )-#if MIN_VERSION_persistent(1, 1, 0)-           => (Text -> Maybe (Unique user))-#else-           => (Text -> Maybe (Unique user b))-#endif-           -> GHandler Auth y ()-postLoginR uniq = do-    (mu,mp) <- runInputPost $ (,)-        <$> iopt textField "username"-        <*> iopt textField "password"--    isValid <- fromMaybe (return False) -                 (validateUser <$> (uniq =<< mu) <*> mp)-    if isValid -       then setCreds True $ Creds "hashdb" (fromMaybe "" mu) []-       else do setMessage "Invalid username/password"-               toMaster <- getRouteToMaster-               redirect $ toMaster LoginR----- | A drop in for the getAuthId method of your YesodAuth instance which---   can be used if authHashDB is the only plugin in use.-getAuthIdHashDB :: ( YesodAuth master, YesodPersist master-                   , HashDBUser user, PersistEntity user-#if MIN_VERSION_persistent(1, 1, 0)-                   , Key user ~ AuthId master-                   , b ~ YesodPersistBackend master-                   , PersistMonadBackend (b (GHandler sub master)) ~ PersistEntityBackend user-                   , PersistUnique (b (GHandler sub master))-#else-                   , Key b user ~ AuthId master-                   , b ~ YesodPersistBackend master-                   , b ~ PersistEntityBackend user-                   , PersistUnique b (GHandler sub master)-                   , PersistStore b (GHandler sub master)-#endif-                   )-                => (AuthRoute -> Route master)   -- ^ your site's Auth Route-#if MIN_VERSION_persistent(1, 1, 0)-                -> (Text -> Maybe (Unique user)) -- ^ gets user ID-#else-                -> (Text -> Maybe (Unique user b)) -- ^ gets user ID-#endif-                -> Creds master                  -- ^ the creds argument-                -> GHandler sub master (Maybe (AuthId master))-getAuthIdHashDB authR uniq creds = do-    muid <- maybeAuthId-    case muid of-        -- user already authenticated-        Just uid -> return $ Just uid-        Nothing       -> do-            x <- case uniq (credsIdent creds) of-                   Nothing -> return Nothing-                   Just u  -> runDB (getBy u)-            case x of-                -- user exists-                Just (Entity uid _) -> return $ Just uid-                Nothing       -> do-                    setMessage "User not found"-                    redirect $ authR LoginR---- | Prompt for username and password, validate that against a database---   which holds the username and a hash of the password-authHashDB :: ( YesodAuth m, YesodPersist m-              , HashDBUser user-              , PersistEntity user-#if MIN_VERSION_persistent(1, 1, 0)-              , b ~ YesodPersistBackend m-              , PersistMonadBackend (b (GHandler Auth m)) ~ PersistEntityBackend user-              , PersistUnique (b (GHandler Auth m)))-           => (Text -> Maybe (Unique user)) -> AuthPlugin m-#else-              , b ~ YesodPersistBackend m-              , b ~ PersistEntityBackend user-              , PersistStore b (GHandler Auth m)-              , PersistUnique b (GHandler Auth m))-           => (Text -> Maybe (Unique user b)) -> AuthPlugin m-#endif-authHashDB uniq = AuthPlugin "hashdb" dispatch $ \tm -> toWidget [hamlet|-$newline never-    <div id="header">-        <h1>Login--    <div id="login">-        <form method="post" action="@{tm login}">-            <table>-                <tr>-                    <th>Username:-                    <td>-                        <input id="x" name="username" autofocus="" required>-                <tr>-                    <th>Password:-                    <td>-                        <input type="password" name="password" required>-                <tr>-                    <td>&nbsp;-                    <td>-                        <input type="submit" value="Login">--            <script>-                if (!("autofocus" in document.createElement("input"))) {-                    document.getElementById("x").focus();-                }--|]-    where-        dispatch "POST" ["login"] = postLoginR uniq >>= sendResponse-        dispatch _ _              = notFound---------------------------------------------------------------------- Predefined datatype--------------------------------------------------------------------- | Generate data base instances for a valid user-share [mkPersist sqlSettings, mkMigrate "migrateUsers"]-         [persistUpperCase|-User-    username Text Eq-    password Text-    salt     Text-    UniqueUser username-|]--instance HashDBUser (UserGeneric backend) where-  userPasswordHash = Just . userPassword-  userPasswordSalt = Just . userSalt-  setSaltAndPasswordHash s h u = u { userSalt     = s-                               , userPassword = h-                               }
Yesod/Auth/Message.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+ module Yesod.Auth.Message     ( AuthMessage (..)     , defaultMessage@@ -12,9 +13,17 @@     , norwegianBokmålMessage     , japaneseMessage     , finnishMessage+    , chineseMessage+    , croatianMessage+    , spanishMessage+    , czechMessage+    , russianMessage+    , dutchMessage+    , danishMessage+    , koreanMessage+    , romanianMessage     ) where -import Data.Monoid (mappend) import Data.Text (Text)  data AuthMessage =@@ -23,6 +32,8 @@     | LoginGoogle     | LoginYahoo     | Email+    | UserName+    | IdentifierNotFound Text     | Password     | Register     | RegisterLong@@ -30,6 +41,8 @@     | ConfirmationEmailSentTitle     | ConfirmationEmailSent Text     | AddressVerified+    | EmailVerifiedChangePass+    | EmailVerified     | InvalidKeyTitle     | InvalidKey     | InvalidEmailPass@@ -47,6 +60,19 @@     | LoginTitle     | PleaseProvideUsername     | PleaseProvidePassword+    | NoIdentifierProvided+    | InvalidEmailAddress+    | PasswordResetTitle+    | ProvideIdentifier+    | SendPasswordResetEmail+    | PasswordResetPrompt+    | CurrentPassword+    | InvalidUsernamePass+    | Logout+    | LogoutTitle+    | AuthError+{-# DEPRECATED Logout "Please, use LogoutTitle instead." #-}+{-# DEPRECATED AddressVerified "Please, use EmailVerifiedChangePass instead." #-}  -- | Defaults to 'englishMessage'. defaultMessage :: AuthMessage -> Text@@ -54,20 +80,22 @@  englishMessage :: AuthMessage -> Text englishMessage NoOpenID = "No OpenID identifier found"-englishMessage LoginOpenID = "Login via OpenID"-englishMessage LoginGoogle = "Login via Google"-englishMessage LoginYahoo = "Login via Yahoo"+englishMessage LoginOpenID = "Log in via OpenID"+englishMessage LoginGoogle = "Log in via Google"+englishMessage LoginYahoo = "Log in via Yahoo" englishMessage Email = "Email"+englishMessage UserName = "User name" englishMessage Password = "Password"+englishMessage CurrentPassword = "Current Password" englishMessage Register = "Register" englishMessage RegisterLong = "Register a new account" englishMessage EnterEmail = "Enter your e-mail address below, and a confirmation e-mail will be sent to you." englishMessage ConfirmationEmailSentTitle = "Confirmation e-mail sent" englishMessage (ConfirmationEmailSent email) =-    "A confirmation e-mail has been sent to " `mappend`-    email `mappend`-    "."-englishMessage AddressVerified = "Address verified, please set a new password"+    "A confirmation e-mail has been sent to " `mappend` email `mappend` "."+englishMessage AddressVerified = "Email address verified, please set a new password"+englishMessage EmailVerifiedChangePass = "Email address verified, please set a new password"+englishMessage EmailVerified = "Email address verified" englishMessage InvalidKeyTitle = "Invalid verification key" englishMessage InvalidKey = "I'm sorry, but that was an invalid verification key." englishMessage InvalidEmailPass = "Invalid email/password combination"@@ -78,13 +106,24 @@ englishMessage ConfirmPass = "Confirm" englishMessage PassMismatch = "Passwords did not match, please try again" englishMessage PassUpdated = "Password updated"-englishMessage Facebook = "Login with Facebook"-englishMessage LoginViaEmail = "Login via email"+englishMessage Facebook = "Log in with Facebook"+englishMessage LoginViaEmail = "Log in via email" englishMessage InvalidLogin = "Invalid login" englishMessage NowLoggedIn = "You are now logged in"-englishMessage LoginTitle = "Login"+englishMessage LoginTitle = "Log In" englishMessage PleaseProvideUsername = "Please fill in your username" englishMessage PleaseProvidePassword = "Please fill in your password"+englishMessage NoIdentifierProvided = "No email/username provided"+englishMessage InvalidEmailAddress = "Invalid email address provided"+englishMessage PasswordResetTitle = "Password Reset"+englishMessage ProvideIdentifier = "Email or Username"+englishMessage SendPasswordResetEmail = "Send password reset email"+englishMessage PasswordResetPrompt = "Enter your e-mail address or username below, and a password reset e-mail will be sent to you."+englishMessage InvalidUsernamePass = "Invalid username/password combination"+englishMessage (IdentifierNotFound ident) = "Login not found: " `mappend` ident+englishMessage Logout = "Log Out"+englishMessage LogoutTitle = "Log Out"+englishMessage AuthError = "Authentication Error" -- FIXME by Google Translate  portugueseMessage :: AuthMessage -> Text portugueseMessage NoOpenID = "Nenhum identificador OpenID encontrado"@@ -92,7 +131,9 @@ portugueseMessage LoginGoogle = "Entrar via Google" portugueseMessage LoginYahoo = "Entrar via Yahoo" portugueseMessage Email = "E-mail"+portugueseMessage UserName = "Nome de usuário" -- FIXME by Google Translate "user name" portugueseMessage Password = "Senha"+portugueseMessage CurrentPassword = "Palavra de passe" portugueseMessage Register = "Registrar" portugueseMessage RegisterLong = "Registrar uma nova conta" portugueseMessage EnterEmail = "Por favor digite seu endereço de e-mail abaixo e um e-mail de confirmação será enviado para você."@@ -102,6 +143,8 @@     email `mappend`     "." portugueseMessage AddressVerified = "Endereço verificado, por favor entre com uma nova senha"+portugueseMessage EmailVerifiedChangePass = "Endereço verificado, por favor entre com uma nova senha"+portugueseMessage EmailVerified = "Endereço verificado" portugueseMessage InvalidKeyTitle = "Chave de verificação inválida" portugueseMessage InvalidKey = "Por favor nos desculpe, mas essa é uma chave de verificação inválida." portugueseMessage InvalidEmailPass = "E-mail e/ou senha inválidos"@@ -119,14 +162,78 @@ portugueseMessage LoginTitle = "Entrar no site" portugueseMessage PleaseProvideUsername = "Por favor digite seu nome de usuário" portugueseMessage PleaseProvidePassword = "Por favor digite sua senha"+portugueseMessage NoIdentifierProvided = "Nenhum e-mail ou nome de usuário informado"+portugueseMessage InvalidEmailAddress = "Endereço de e-mail inválido informado"+portugueseMessage PasswordResetTitle = "Resetar senha"+portugueseMessage ProvideIdentifier = "E-mail ou nome de usuário"+portugueseMessage SendPasswordResetEmail = "Enviar e-mail para resetar senha"+portugueseMessage PasswordResetPrompt = "Insira seu endereço de e-mail ou nome de usuário abaixo.  Um e-mail para resetar sua senha será enviado para você."+portugueseMessage InvalidUsernamePass = "Nome de usuário ou senha inválidos"+-- TODO+portugueseMessage i@(IdentifierNotFound _) = englishMessage i+portugueseMessage Logout = "Sair" -- FIXME by Google Translate+portugueseMessage LogoutTitle = "Sair" -- FIXME by Google Translate+portugueseMessage AuthError = "Erro de autenticação" -- FIXME by Google Translate +spanishMessage :: AuthMessage -> Text+spanishMessage NoOpenID = "No se encuentra el identificador OpenID"+spanishMessage LoginOpenID = "Entrar utilizando OpenID"+spanishMessage LoginGoogle = "Entrar utilizando Google"+spanishMessage LoginYahoo = "Entrar utilizando Yahoo"+spanishMessage Email = "Correo electrónico"+spanishMessage UserName = "Nombre de Usuario"+spanishMessage Password = "Contraseña"+spanishMessage CurrentPassword = "Contraseña actual"+spanishMessage Register = "Registrarse"+spanishMessage RegisterLong = "Registrar una nueva cuenta"+spanishMessage EnterEmail = "Coloque su dirección de correo electrónico, y un correo de confirmación le será enviado a su cuenta."+spanishMessage ConfirmationEmailSentTitle = "La confirmación de correo ha sido enviada"+spanishMessage (ConfirmationEmailSent email) =+    "Una confirmación de correo electrónico ha sido enviada a " `mappend`+    email `mappend`+    "."+spanishMessage AddressVerified = "Dirección verificada, por favor introduzca una contraseña"+spanishMessage EmailVerifiedChangePass = "Dirección verificada, por favor introduzca una contraseña"+spanishMessage EmailVerified = "Dirección verificada"+spanishMessage InvalidKeyTitle = "Clave de verificación invalida"+spanishMessage InvalidKey = "Lo sentimos, pero esa clave de verificación es inválida."+spanishMessage InvalidEmailPass = "La combinación cuenta de correo/contraseña es inválida"+spanishMessage BadSetPass = "Debe acceder a la aplicación para modificar la contraseña"+spanishMessage SetPassTitle = "Modificar contraseña"+spanishMessage SetPass = "Actualizar nueva contraseña"+spanishMessage NewPass = "Nueva contraseña"+spanishMessage ConfirmPass = "Confirmar"+spanishMessage PassMismatch = "Las contraseñas no coinciden, inténtelo de nuevo"+spanishMessage PassUpdated = "Contraseña actualizada"+spanishMessage Facebook = "Entrar mediante Facebook"+spanishMessage LoginViaEmail = "Entrar mediante una cuenta de correo"+spanishMessage InvalidLogin = "Login inválido"+spanishMessage NowLoggedIn = "Usted ha ingresado al sitio"+spanishMessage LoginTitle = "Log In"+spanishMessage PleaseProvideUsername = "Por favor escriba su nombre de usuario"+spanishMessage PleaseProvidePassword = "Por favor escriba su contraseña"+spanishMessage NoIdentifierProvided = "No ha indicado una cuenta de correo/nombre de usuario"+spanishMessage InvalidEmailAddress = "La cuenta de correo es inválida"+spanishMessage PasswordResetTitle = "Actualización de contraseña"+spanishMessage ProvideIdentifier = "Cuenta de correo o nombre de usuario"+spanishMessage SendPasswordResetEmail = "Enviar correo de actualización de contraseña"+spanishMessage PasswordResetPrompt = "Escriba su cuenta de correo o nombre de usuario, y una confirmación de actualización de contraseña será enviada a su cuenta de correo."+spanishMessage InvalidUsernamePass = "Combinación de nombre de usuario/contraseña invalida"+-- TODO+spanishMessage i@(IdentifierNotFound _) = englishMessage i+spanishMessage Logout = "Finalizar la sesión" -- FIXME by Google Translate+spanishMessage LogoutTitle = "Finalizar la sesión" -- FIXME by Google Translate+spanishMessage AuthError = "Error de autenticación" -- FIXME by Google Translate+ swedishMessage :: AuthMessage -> Text swedishMessage NoOpenID = "Fann ej OpenID identifierare" swedishMessage LoginOpenID = "Logga in via OpenID" swedishMessage LoginGoogle = "Logga in via Google" swedishMessage LoginYahoo = "Logga in via Yahoo" swedishMessage Email = "Epost"+swedishMessage UserName = "Användarnamn"  -- FIXME by Google Translate "user name" swedishMessage Password = "Lösenord"+swedishMessage CurrentPassword = "Current password" swedishMessage Register = "Registrera" swedishMessage RegisterLong = "Registrera ett nytt konto" swedishMessage EnterEmail = "Skriv in din epost nedan så kommer ett konfirmationsmail skickas till adressen."@@ -136,6 +243,8 @@     email `mappend`     "." swedishMessage AddressVerified = "Adress verifierad, vänligen välj nytt lösenord"+swedishMessage EmailVerifiedChangePass = "Adress verifierad, vänligen välj nytt lösenord"+swedishMessage EmailVerified = "Adress verifierad" swedishMessage InvalidKeyTitle = "Ogiltig verifikationsnyckel" swedishMessage InvalidKey = "Tyvärr, du angav en ogiltig verifimationsnyckel." swedishMessage InvalidEmailPass = "Ogiltig epost/lösenord kombination"@@ -153,23 +262,40 @@ swedishMessage LoginTitle = "Logga in" swedishMessage PleaseProvideUsername = "Vänligen fyll i användarnamn" swedishMessage PleaseProvidePassword = "Vänligen fyll i lösenord"+swedishMessage NoIdentifierProvided = "Emailadress eller användarnamn saknas"+swedishMessage InvalidEmailAddress = "Ogiltig emailadress angiven"+swedishMessage PasswordResetTitle = "Återställning av lösenord"+swedishMessage ProvideIdentifier = "Epost eller användarnamn"+swedishMessage SendPasswordResetEmail = "Skicka email för återställning av lösenord"+swedishMessage PasswordResetPrompt = "Skriv in din emailadress eller användarnamn nedan och " `mappend`+                                     "ett email för återställning av lösenord kommmer att skickas till dig."+swedishMessage InvalidUsernamePass = "Ogiltig kombination av användarnamn och lösenord"+-- TODO+swedishMessage i@(IdentifierNotFound _) = englishMessage i+swedishMessage Logout = "Loggar ut" -- FIXME by Google Translate+swedishMessage LogoutTitle = "Loggar ut" -- FIXME by Google Translate+swedishMessage AuthError = "Autentisering Fel" -- FIXME by Google Translate  germanMessage :: AuthMessage -> Text germanMessage NoOpenID = "Kein OpenID-Identifier gefunden" germanMessage LoginOpenID = "Login via OpenID" germanMessage LoginGoogle = "Login via Google" germanMessage LoginYahoo = "Login via Yahoo"-germanMessage Email = "Email"+germanMessage Email = "E-Mail"+germanMessage UserName = "Benutzername" germanMessage Password = "Passwort"+germanMessage CurrentPassword = "Aktuelles Passwort" germanMessage Register = "Registrieren" germanMessage RegisterLong = "Neuen Account registrieren"-germanMessage EnterEmail = "Bitte die e-Mail Adresse angeben, eine Bestätigungsmail wird verschickt."+germanMessage EnterEmail = "Bitte die E-Mail Adresse angeben, eine Bestätigungsmail wird verschickt." germanMessage ConfirmationEmailSentTitle = "Bestätigung verschickt." germanMessage (ConfirmationEmailSent email) =     "Eine Bestätigung wurde an " `mappend`     email `mappend`-    "versandt."+    " versandt." germanMessage AddressVerified = "Adresse bestätigt, bitte neues Passwort angeben"+germanMessage EmailVerifiedChangePass = "Adresse bestätigt, bitte neues Passwort angeben"+germanMessage EmailVerified = "Adresse bestätigt" germanMessage InvalidKeyTitle = "Ungültiger Bestätigungsschlüssel" germanMessage InvalidKey = "Das war leider ein ungültiger Bestätigungsschlüssel" germanMessage InvalidEmailPass = "Ungültiger Nutzername oder Passwort"@@ -178,17 +304,26 @@ germanMessage SetPass = "Neues Passwort angeben" germanMessage NewPass = "Neues Passwort" germanMessage ConfirmPass = "Bestätigen"-germanMessage PassMismatch = "Die Passwörter stimmten nicht überein"+germanMessage PassMismatch = "Die Passwörter stimmen nicht überein" germanMessage PassUpdated = "Passwort überschrieben" germanMessage Facebook = "Login über Facebook"-germanMessage LoginViaEmail = "Login via e-Mail"+germanMessage LoginViaEmail = "Login via E-Mail" germanMessage InvalidLogin = "Ungültiger Login" germanMessage NowLoggedIn = "Login erfolgreich"-germanMessage LoginTitle = "Login"+germanMessage LoginTitle = "Anmelden" germanMessage PleaseProvideUsername = "Bitte Nutzername angeben" germanMessage PleaseProvidePassword = "Bitte Passwort angeben"--+germanMessage NoIdentifierProvided = "Keine E-Mail-Adresse oder kein Nutzername angegeben"+germanMessage InvalidEmailAddress = "Unzulässiger E-Mail-Anbieter"+germanMessage PasswordResetTitle = "Passwort zurücksetzen"+germanMessage ProvideIdentifier = "E-Mail-Adresse oder Nutzername"+germanMessage SendPasswordResetEmail = "E-Mail zusenden um Passwort zurückzusetzen"+germanMessage PasswordResetPrompt = "Nach Einhabe der E-Mail-Adresse oder des Nutzernamen wird eine E-Mail zugesendet mit welcher das Passwort zurückgesetzt werden kann."+germanMessage InvalidUsernamePass = "Ungültige Kombination aus Nutzername und Passwort"+germanMessage i@(IdentifierNotFound _) = englishMessage i -- TODO+germanMessage Logout = "Abmelden"+germanMessage LogoutTitle = "Abmelden"+germanMessage AuthError = "Fehler beim Anmelden"  frenchMessage :: AuthMessage -> Text frenchMessage NoOpenID = "Aucun fournisseur OpenID n'a été trouvé"@@ -196,7 +331,9 @@ frenchMessage LoginGoogle = "Se connecter avec Google" frenchMessage LoginYahoo = "Se connecter avec Yahoo" frenchMessage Email = "Adresse électronique"+frenchMessage UserName = "Nom d'utilisateur" -- FIXME by Google Translate "user name" frenchMessage Password = "Mot de passe"+frenchMessage CurrentPassword = "Mot de passe actuel" frenchMessage Register = "S'inscrire" frenchMessage RegisterLong = "Créer un compte" frenchMessage EnterEmail = "Entrez ci-dessous votre adresse électronique, et un message de confirmation vous sera envoyé"@@ -206,9 +343,11 @@     email `mappend`     "." frenchMessage AddressVerified = "Votre adresse électronique a été validée, merci de choisir un nouveau mot de passe."+frenchMessage EmailVerifiedChangePass = "Votre adresse électronique a été validée, merci de choisir un nouveau mot de passe."+frenchMessage EmailVerified = "Votre adresse électronique a été validée" frenchMessage InvalidKeyTitle = "Clef de validation incorrecte" frenchMessage InvalidKey = "Désolé, mais cette clef de validation est incorrecte"-frenchMessage InvalidEmailPass = "Le couple mot de passe/adresse électronique n'est pas correct"+frenchMessage InvalidEmailPass = "La combinaison de ce mot de passe et de cette adresse électronique n'existe pas." frenchMessage BadSetPass = "Vous devez être connecté pour choisir un mot de passe" frenchMessage SetPassTitle = "Changer de mot de passe" frenchMessage SetPass = "Choisir un nouveau mot de passe"@@ -217,12 +356,23 @@ frenchMessage PassMismatch = "Le deux mots de passe sont différents, veuillez les corriger" frenchMessage PassUpdated = "Le mot de passe a bien été changé" frenchMessage Facebook = "Se connecter avec Facebook"-frenchMessage LoginViaEmail = "Se connecter à l'aide d'une adresse électronique"+frenchMessage LoginViaEmail = "Se connecter avec une adresse électronique" frenchMessage InvalidLogin = "Nom d'utilisateur incorrect" frenchMessage NowLoggedIn = "Vous êtes maintenant connecté" frenchMessage LoginTitle = "Se connecter"-frenchMessage PleaseProvideUsername = "Merci de renseigner votre nom d'utilisateur"-frenchMessage PleaseProvidePassword = "Merci de spécifier un mot de passe"+frenchMessage PleaseProvideUsername = "Veuillez fournir votre nom d'utilisateur"+frenchMessage PleaseProvidePassword = "Veuillez fournir votre mot de passe"+frenchMessage NoIdentifierProvided = "Adresse électronique/nom d'utilisateur non spécifié"+frenchMessage InvalidEmailAddress = "Adresse électronique spécifiée invalide"+frenchMessage PasswordResetTitle = "Réinitialisation du mot de passe"+frenchMessage ProvideIdentifier = "Adresse électronique ou nom d'utilisateur"+frenchMessage SendPasswordResetEmail = "Envoi d'un courriel pour réinitialiser le mot de passe"+frenchMessage PasswordResetPrompt = "Entrez votre courriel ou votre nom d'utilisateur ci-dessous, et vous recevrez un message électronique pour réinitialiser votre mot de passe."+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 Logout = "Déconnexion"+frenchMessage LogoutTitle = "Déconnexion"+frenchMessage AuthError = "Erreur d'authentification" -- FIXME by Google Translate  norwegianBokmålMessage :: AuthMessage -> Text norwegianBokmålMessage NoOpenID = "Ingen OpenID-identifiserer funnet"@@ -230,7 +380,9 @@ norwegianBokmålMessage LoginGoogle = "Logg inn med Google" norwegianBokmålMessage LoginYahoo = "Logg inn med Yahoo" norwegianBokmålMessage Email = "E-post"+norwegianBokmålMessage UserName = "Brukernavn" -- FIXME by Google Translate "user name" norwegianBokmålMessage Password = "Passord"+norwegianBokmålMessage CurrentPassword = "Current password" norwegianBokmålMessage Register = "Registrer" norwegianBokmålMessage RegisterLong = "Registrer en ny konto" norwegianBokmålMessage EnterEmail = "Skriv inn e-postadressen din nedenfor og en e-postkonfirmasjon vil bli sendt."@@ -240,6 +392,8 @@     email `mappend`     "." norwegianBokmålMessage AddressVerified = "Adresse verifisert, vennligst sett et nytt passord."+norwegianBokmålMessage EmailVerifiedChangePass = "Adresse verifisert, vennligst sett et nytt passord."+norwegianBokmålMessage EmailVerified = "Adresse verifisert" norwegianBokmålMessage InvalidKeyTitle = "Ugyldig verifiseringsnøkkel" norwegianBokmålMessage InvalidKey = "Beklager, men det var en ugyldig verifiseringsnøkkel." norwegianBokmålMessage InvalidEmailPass = "Ugyldig e-post/passord-kombinasjon"@@ -257,6 +411,18 @@ norwegianBokmålMessage LoginTitle = "Logg inn" norwegianBokmålMessage PleaseProvideUsername = "Vennligst fyll inn ditt brukernavn" norwegianBokmålMessage PleaseProvidePassword = "Vennligst fyll inn ditt passord"+norwegianBokmålMessage NoIdentifierProvided = "No email/username provided"+norwegianBokmålMessage InvalidEmailAddress = "Invalid email address provided"+norwegianBokmålMessage PasswordResetTitle = "Password Reset"+norwegianBokmålMessage ProvideIdentifier = "Email or Username"+norwegianBokmålMessage SendPasswordResetEmail = "Send password reset email"+norwegianBokmålMessage PasswordResetPrompt = "Enter your e-mail address or username below, and a password reset e-mail will be sent to you."+norwegianBokmålMessage InvalidUsernamePass = "Invalid username/password combination"+-- TODO+norwegianBokmålMessage i@(IdentifierNotFound _) = englishMessage i+norwegianBokmålMessage Logout = "Logge ut" -- FIXME by Google Translate+norwegianBokmålMessage LogoutTitle = "Logge ut" -- FIXME by Google Translate+norwegianBokmålMessage AuthError = "Godkjenningsfeil" -- FIXME by Google Translate  japaneseMessage :: AuthMessage -> Text japaneseMessage NoOpenID = "OpenID識別子がありません"@@ -264,7 +430,9 @@ japaneseMessage LoginGoogle = "Googleでログイン" japaneseMessage LoginYahoo = "Yahooでログイン" japaneseMessage Email = "Eメール"+japaneseMessage UserName = "ユーザー名" -- FIXME by Google Translate "user name" japaneseMessage Password = "パスワード"+japaneseMessage CurrentPassword = "現在のパスワード" japaneseMessage Register = "登録" japaneseMessage RegisterLong = "新規アカウント登録" japaneseMessage EnterEmail = "メールアドレスを入力してください。確認メールが送られます"@@ -274,6 +442,8 @@     email `mappend`     " に送信しました" japaneseMessage AddressVerified = "アドレスは認証されました。新しいパスワードを設定してください"+japaneseMessage EmailVerifiedChangePass = "アドレスは認証されました。新しいパスワードを設定してください"+japaneseMessage EmailVerified = "アドレスは認証されました" japaneseMessage InvalidKeyTitle = "認証キーが無効です" japaneseMessage InvalidKey = "申し訳ありません。無効な認証キーです" japaneseMessage InvalidEmailPass = "メールアドレスまたはパスワードが無効です"@@ -291,6 +461,18 @@ japaneseMessage LoginTitle = "ログイン" japaneseMessage PleaseProvideUsername = "ユーザ名を入力してください" japaneseMessage PleaseProvidePassword = "パスワードを入力してください"+japaneseMessage NoIdentifierProvided = "メールアドレス/ユーザ名が入力されていません"+japaneseMessage InvalidEmailAddress = "メールアドレスが無効です"+japaneseMessage PasswordResetTitle = "パスワードの再設定"+japaneseMessage ProvideIdentifier = "メールアドレスまたはユーザ名"+japaneseMessage SendPasswordResetEmail = "パスワード再設定用メールの送信"+japaneseMessage PasswordResetPrompt = "以下にメールアドレスまたはユーザ名を入力してください。パスワードを再設定するためのメールが送信されます。"+japaneseMessage InvalidUsernamePass = "ユーザ名とパスワードの組み合わせが間違っています"+japaneseMessage (IdentifierNotFound ident) =+  ident `mappend` "は登録されていません"+japaneseMessage Logout = "ログアウト" -- FIXME by Google Translate+japaneseMessage LogoutTitle = "ログアウト" -- FIXME by Google Translate+japaneseMessage AuthError = "認証エラー" -- FIXME by Google Translate  finnishMessage :: AuthMessage -> Text finnishMessage NoOpenID = "OpenID-tunnistetta ei löydy"@@ -298,7 +480,9 @@ finnishMessage LoginGoogle = "Kirjaudu Google-tilillä" finnishMessage LoginYahoo = "Kirjaudu Yahoo-tilillä" finnishMessage Email = "Sähköposti"+finnishMessage UserName = "Käyttäjätunnus" -- FIXME by Google Translate "user name" finnishMessage Password = "Salasana"+finnishMessage CurrentPassword = "Current password" finnishMessage Register = "Luo uusi" finnishMessage RegisterLong = "Luo uusi tili" finnishMessage EnterEmail = "Kirjoita alle sähköpostiosoitteesi, johon vahvistussähköposti lähetetään."@@ -307,7 +491,10 @@     "Vahvistussähköposti on lähetty osoitteeseen " `mappend`     email `mappend`     "."+ finnishMessage AddressVerified = "Sähköpostiosoite vahvistettu. Anna uusi salasana"+finnishMessage EmailVerifiedChangePass = "Sähköpostiosoite vahvistettu. Anna uusi salasana"+finnishMessage EmailVerified = "Sähköpostiosoite vahvistettu" finnishMessage InvalidKeyTitle = "Virheellinen varmistusavain" finnishMessage InvalidKey = "Valitettavasti varmistusavain on virheellinen." finnishMessage InvalidEmailPass = "Virheellinen sähköposti tai salasana."@@ -325,5 +512,407 @@ finnishMessage LoginTitle = "Kirjautuminen" finnishMessage PleaseProvideUsername = "Käyttäjänimi puuttuu" finnishMessage PleaseProvidePassword = "Salasana puuttuu"+finnishMessage NoIdentifierProvided = "Sähköpostiosoite/käyttäjänimi puuttuu"+finnishMessage InvalidEmailAddress = "Annettu sähköpostiosoite ei kelpaa"+finnishMessage PasswordResetTitle = "Uuden salasanan tilaaminen"+finnishMessage ProvideIdentifier = "Sähköpostiosoite tai käyttäjänimi"+finnishMessage SendPasswordResetEmail = "Lähetä uusi salasana sähköpostitse"+finnishMessage PasswordResetPrompt = "Anna sähköpostiosoitteesi tai käyttäjätunnuksesi alla, niin lähetämme uuden salasanan sähköpostitse."+finnishMessage InvalidUsernamePass = "Virheellinen käyttäjänimi tai salasana."+-- TODO+finnishMessage i@(IdentifierNotFound _) = englishMessage i+finnishMessage Logout = "Kirjaudu ulos" -- FIXME by Google Translate+finnishMessage LogoutTitle = "Kirjaudu ulos" -- FIXME by Google Translate+finnishMessage AuthError = "Authentication Error" -- FIXME by Google Translate +chineseMessage :: AuthMessage -> Text+chineseMessage NoOpenID = "无效的OpenID"+chineseMessage LoginOpenID = "用OpenID登录"+chineseMessage LoginGoogle = "用Google帐户登录"+chineseMessage LoginYahoo = "用Yahoo帐户登录"+chineseMessage Email = "邮箱"+chineseMessage UserName = "用户名"+chineseMessage Password = "密码"+chineseMessage CurrentPassword = "当前密码"+chineseMessage Register = "注册"+chineseMessage RegisterLong = "注册新帐户"+chineseMessage EnterEmail = "输入你的邮箱地址,你将收到一封确认邮件。"+chineseMessage ConfirmationEmailSentTitle = "确认邮件已发送"+chineseMessage (ConfirmationEmailSent email) =+    "确认邮件已发送至 " `mappend`+    email `mappend`+    "."+chineseMessage AddressVerified = "地址验证成功,请设置新密码"+chineseMessage EmailVerifiedChangePass = "地址验证成功,请设置新密码"+chineseMessage EmailVerified = "地址验证成功"+chineseMessage InvalidKeyTitle = "无效的验证码"+chineseMessage InvalidKey = "对不起,验证码无效。"+chineseMessage InvalidEmailPass = "无效的邮箱/密码组合"+chineseMessage BadSetPass = "你需要登录才能设置密码"+chineseMessage SetPassTitle = "设置密码"+chineseMessage SetPass = "设置新密码"+chineseMessage NewPass = "新密码"+chineseMessage ConfirmPass = "确认"+chineseMessage PassMismatch = "密码不匹配,请重新输入"+chineseMessage PassUpdated = "密码更新成功"+chineseMessage Facebook = "用Facebook帐户登录"+chineseMessage LoginViaEmail = "用邮箱登录"+chineseMessage InvalidLogin = "登录失败"+chineseMessage NowLoggedIn = "登录成功"+chineseMessage LoginTitle = "登录"+chineseMessage PleaseProvideUsername = "请输入用户名"+chineseMessage PleaseProvidePassword = "请输入密码"+chineseMessage NoIdentifierProvided = "缺少邮箱/用户名"+chineseMessage InvalidEmailAddress = "无效的邮箱地址"+chineseMessage PasswordResetTitle = "重置密码"+chineseMessage ProvideIdentifier = "邮箱或用户名"+chineseMessage SendPasswordResetEmail = "发送密码重置邮件"+chineseMessage PasswordResetPrompt = "输入你的邮箱地址或用户名,你将收到一封密码重置邮件。"+chineseMessage InvalidUsernamePass = "无效的用户名/密码组合"+chineseMessage (IdentifierNotFound ident) = "邮箱/用户名不存在: " `mappend` ident+chineseMessage Logout = "注销"+chineseMessage LogoutTitle = "注销"+chineseMessage AuthError = "验证错误" +czechMessage :: AuthMessage -> Text+czechMessage NoOpenID = "Nebyl nalezen identifikátor OpenID"+czechMessage LoginOpenID = "Přihlásit přes OpenID"+czechMessage LoginGoogle = "Přihlásit přes Google"+czechMessage LoginYahoo = "Přihlásit přes Yahoo"+czechMessage Email = "E-mail"+czechMessage UserName = "Uživatelské jméno"+czechMessage Password = "Heslo"+czechMessage CurrentPassword = "Current password"+czechMessage Register = "Registrovat"+czechMessage RegisterLong = "Zaregistrovat nový účet"+czechMessage EnterEmail = "Níže zadejte svou e-mailovou adresu a bude vám poslán potvrzovací e-mail."+czechMessage ConfirmationEmailSentTitle = "Potvrzovací e-mail odeslán"+czechMessage (ConfirmationEmailSent email) =+    "Potvrzovací e-mail byl odeslán na " `mappend` email `mappend` "."+czechMessage AddressVerified = "Adresa byla ověřena, prosím nastavte si nové heslo"+czechMessage EmailVerifiedChangePass = "Adresa byla ověřena, prosím nastavte si nové heslo"+czechMessage EmailVerified = "Adresa byla ověřena"+czechMessage InvalidKeyTitle = "Neplatný ověřovací klíč"+czechMessage InvalidKey = "Bohužel, ověřovací klíč je neplatný."+czechMessage InvalidEmailPass = "Neplatná kombinace e-mail/heslo"+czechMessage BadSetPass = "Pro nastavení hesla je vyžadováno přihlášení"+czechMessage SetPassTitle = "Nastavit heslo"+czechMessage SetPass = "Nastavit nové heslo"+czechMessage NewPass = "Nové heslo"+czechMessage ConfirmPass = "Potvrdit"+czechMessage PassMismatch = "Hesla si neodpovídají, zkuste to znovu"+czechMessage PassUpdated = "Heslo aktualizováno"+czechMessage Facebook = "Přihlásit přes Facebook"+czechMessage LoginViaEmail = "Přihlásit přes e-mail"+czechMessage InvalidLogin = "Neplatné přihlášení"+czechMessage NowLoggedIn = "Přihlášení proběhlo úspěšně"+czechMessage LoginTitle = "Přihlásit"+czechMessage PleaseProvideUsername = "Prosím, zadejte svoje uživatelské jméno"+czechMessage PleaseProvidePassword = "Prosím, zadejte svoje heslo"+czechMessage NoIdentifierProvided = "Nebyl poskytnut žádný e-mail nebo uživatelské jméno"+czechMessage InvalidEmailAddress = "Zadaná e-mailová adresa je neplatná"+czechMessage PasswordResetTitle = "Obnovení hesla"+czechMessage ProvideIdentifier = "E-mail nebo uživatelské jméno"+czechMessage SendPasswordResetEmail = "Poslat e-mail pro obnovení hesla"+czechMessage PasswordResetPrompt = "Zadejte svou e-mailovou adresu nebo uživatelské jméno a bude vám poslán email pro obnovení hesla."+czechMessage InvalidUsernamePass = "Neplatná kombinace uživatelského jména a hesla"+-- TODO+czechMessage i@(IdentifierNotFound _) = englishMessage i+czechMessage Logout = "Odhlásit" -- FIXME by Google Translate+czechMessage LogoutTitle = "Odhlásit" -- FIXME by Google Translate+czechMessage AuthError = "Chyba ověřování" -- FIXME by Google Translate++-- Так как e-mail – это фактическое сокращение словосочетания electronic mail,+-- для русского перевода так же использовано сокращение: эл.почта+russianMessage :: AuthMessage -> Text+russianMessage NoOpenID = "Идентификатор OpenID не найден"+russianMessage LoginOpenID = "Вход с помощью OpenID"+russianMessage LoginGoogle = "Вход с помощью Google"+russianMessage LoginYahoo = "Вход с помощью Yahoo"+russianMessage Email = "Эл.почта"+russianMessage UserName = "Имя пользователя"+russianMessage Password = "Пароль"+russianMessage CurrentPassword = "Старый пароль"+russianMessage Register = "Регистрация"+russianMessage RegisterLong = "Создать учётную запись"+russianMessage EnterEmail = "Введите свой адрес эл.почты ниже, вам будет отправлено письмо для подтверждения."+russianMessage ConfirmationEmailSentTitle = "Письмо для подтверждения отправлено"+russianMessage (ConfirmationEmailSent email) =+    "Письмо для подтверждения было отправлено на адрес " `mappend`+    email `mappend`+    "."+russianMessage AddressVerified = "Адрес подтверждён. Пожалуйста, установите новый пароль."+russianMessage EmailVerifiedChangePass = "Адрес подтверждён. Пожалуйста, установите новый пароль."+russianMessage EmailVerified = "Адрес подтверждён"+russianMessage InvalidKeyTitle = "Неверный ключ подтверждения"+russianMessage InvalidKey = "Извините, но ключ подтверждения оказался недействительным."+russianMessage InvalidEmailPass = "Неверное сочетание эл.почты и пароля"+russianMessage BadSetPass = "Чтобы изменить пароль, необходимо выполнить вход"+russianMessage SetPassTitle = "Установить пароль"+russianMessage SetPass = "Установить новый пароль"+russianMessage NewPass = "Новый пароль"+russianMessage ConfirmPass = "Подтверждение пароля"+russianMessage PassMismatch = "Пароли не совпадают, повторите снова"+russianMessage PassUpdated = "Пароль обновлён"+russianMessage Facebook = "Войти с помощью Facebook"+russianMessage LoginViaEmail = "Войти по адресу эл.почты"+russianMessage InvalidLogin = "Неверный логин"+russianMessage NowLoggedIn = "Вход выполнен"+russianMessage LoginTitle = "Войти"+russianMessage PleaseProvideUsername = "Пожалуйста, введите ваше имя пользователя"+russianMessage PleaseProvidePassword = "Пожалуйста, введите ваш пароль"+russianMessage NoIdentifierProvided = "Не указан адрес эл.почты/имя пользователя"+russianMessage InvalidEmailAddress = "Указан неверный адрес эл.почты"+russianMessage PasswordResetTitle = "Сброс пароля"+russianMessage ProvideIdentifier = "Имя пользователя или эл.почта"+russianMessage SendPasswordResetEmail = "Отправить письмо для сброса пароля"+russianMessage PasswordResetPrompt = "Введите адрес эл.почты или ваше имя пользователя ниже, вам будет отправлено письмо для сброса пароля."+russianMessage InvalidUsernamePass = "Неверное сочетание имени пользователя и пароля"+russianMessage (IdentifierNotFound ident) = "Логин не найден: " `mappend` ident+russianMessage Logout = "Выйти"+russianMessage LogoutTitle = "Выйти"+russianMessage AuthError = "Ошибка аутентификации"++dutchMessage :: AuthMessage -> Text+dutchMessage NoOpenID = "Geen OpenID identificator gevonden"+dutchMessage LoginOpenID = "Inloggen via OpenID"+dutchMessage LoginGoogle = "Inloggen via Google"+dutchMessage LoginYahoo = "Inloggen via Yahoo"+dutchMessage Email = "E-mail"+dutchMessage UserName = "Gebruikersnaam"+dutchMessage Password = "Wachtwoord"+dutchMessage CurrentPassword = "Huidig wachtwoord"+dutchMessage Register = "Registreren"+dutchMessage RegisterLong = "Registreer een nieuw account"+dutchMessage EnterEmail = "Voer uw e-mailadres hieronder in, er zal een bevestigings-e-mail naar u worden verzonden."+dutchMessage ConfirmationEmailSentTitle = "Bevestigings-e-mail verzonden"+dutchMessage (ConfirmationEmailSent email) =+    "Een bevestigings-e-mail is verzonden naar " `mappend`+    email `mappend`+    "."+dutchMessage AddressVerified = "Adres geverifieerd, stel alstublieft een nieuwe wachtwoord in"+dutchMessage EmailVerifiedChangePass = "Adres geverifieerd, stel alstublieft een nieuwe wachtwoord in"+dutchMessage EmailVerified = "Adres geverifieerd"+dutchMessage InvalidKeyTitle = "Ongeldig verificatietoken"+dutchMessage InvalidKey = "Dat was helaas een ongeldig verificatietoken."+dutchMessage InvalidEmailPass = "Ongeldige e-mailadres/wachtwoord combinatie"+dutchMessage BadSetPass = "U moet ingelogd zijn om een nieuwe wachtwoord in te stellen"+dutchMessage SetPassTitle = "Wachtwoord instellen"+dutchMessage SetPass = "Een nieuwe wachtwoord instellen"+dutchMessage NewPass = "Nieuw wachtwoord"+dutchMessage ConfirmPass = "Bevestig"+dutchMessage PassMismatch = "Wachtwoorden kwamen niet overeen, probeer het alstublieft nog eens"+dutchMessage PassUpdated = "Wachtwoord geüpdatet"+dutchMessage Facebook = "Inloggen met Facebook"+dutchMessage LoginViaEmail = "Inloggen via e-mail"+dutchMessage InvalidLogin = "Ongeldige inloggegevens"+dutchMessage NowLoggedIn = "U bent nu ingelogd"+dutchMessage LoginTitle = "Inloggen"+dutchMessage PleaseProvideUsername = "Voer alstublieft uw gebruikersnaam in"+dutchMessage PleaseProvidePassword = "Voer alstublieft uw wachtwoord in"+dutchMessage NoIdentifierProvided = "Geen e-mailadres/gebruikersnaam opgegeven"+dutchMessage InvalidEmailAddress = "Ongeldig e-mailadres opgegeven"+dutchMessage PasswordResetTitle = "Wachtwoord wijzigen"+dutchMessage ProvideIdentifier = "E-mailadres of gebruikersnaam"+dutchMessage SendPasswordResetEmail = "Stuur een wachtwoord reset e-mail"+dutchMessage PasswordResetPrompt = "Voer uw e-mailadres of gebruikersnaam hieronder in, er zal een e-mail naar u worden verzonden waarmee u uw wachtwoord kunt wijzigen."+dutchMessage InvalidUsernamePass = "Ongeldige gebruikersnaam/wachtwoord combinatie"+dutchMessage (IdentifierNotFound ident) = "Inloggegevens niet gevonden: " `mappend` ident+dutchMessage Logout = "Uitloggen"+dutchMessage LogoutTitle = "Uitloggen"+dutchMessage AuthError = "Verificatiefout"++croatianMessage :: AuthMessage -> Text+croatianMessage NoOpenID = "Nije pronađen OpenID identifikator"+croatianMessage LoginOpenID = "Prijava uz OpenID"+croatianMessage LoginGoogle = "Prijava uz Google"+croatianMessage LoginYahoo = "Prijava uz Yahoo"+croatianMessage Facebook = "Prijava uz Facebook"+croatianMessage LoginViaEmail = "Prijava putem e-pošte"+croatianMessage Email = "E-pošta"+croatianMessage UserName = "Korisničko ime"+croatianMessage Password = "Lozinka"+croatianMessage CurrentPassword = "Current Password"+croatianMessage Register = "Registracija"+croatianMessage RegisterLong = "Registracija novog računa"+croatianMessage EnterEmail = "Dolje unesite adresu e-pošte, pa ćemo vam poslati e-poruku za potvrdu."+croatianMessage PasswordResetPrompt = "Dolje unesite adresu e-pošte ili korisničko ime, pa ćemo vam poslati e-poruku za potvrdu."+croatianMessage ConfirmationEmailSentTitle = "E-poruka za potvrdu"+croatianMessage (ConfirmationEmailSent email) = "E-poruka za potvrdu poslana je na adresu " <> email <> "."+croatianMessage AddressVerified = "Adresa ovjerena, postavite novu lozinku"+croatianMessage EmailVerifiedChangePass = "Adresa ovjerena, postavite novu lozinku"+croatianMessage EmailVerified = "Adresa ovjerena"+croatianMessage InvalidKeyTitle = "Ključ za ovjeru nije valjan"+croatianMessage InvalidKey = "Nažalost, taj ključ za ovjeru nije valjan."+croatianMessage InvalidEmailPass = "Kombinacija e-pošte i lozinke nije valjana"+croatianMessage InvalidUsernamePass = "Kombinacija korisničkog imena i lozinke nije valjana"+croatianMessage BadSetPass = "Za postavljanje lozinke morate biti prijavljeni"+croatianMessage SetPassTitle = "Postavi lozinku"+croatianMessage SetPass = "Postavite novu lozinku"+croatianMessage NewPass = "Nova lozinka"+croatianMessage ConfirmPass = "Potvrda lozinke"+croatianMessage PassMismatch = "Lozinke se ne podudaraju, pokušajte ponovo"+croatianMessage PassUpdated = "Lozinka ažurirana"+croatianMessage InvalidLogin = "Prijava nije valjana"+croatianMessage NowLoggedIn = "Sada ste prijavljeni u"+croatianMessage LoginTitle = "Prijava"+croatianMessage PleaseProvideUsername = "Unesite korisničko ime"+croatianMessage PleaseProvidePassword = "Unesite lozinku"+croatianMessage NoIdentifierProvided = "Nisu dani e-pošta/korisničko ime"+croatianMessage InvalidEmailAddress = "Dana adresa e-pošte nije valjana"+croatianMessage PasswordResetTitle = "Poništavanje lozinke"+croatianMessage ProvideIdentifier = "E-pošta ili korisničko ime"+croatianMessage SendPasswordResetEmail = "Pošalji e-poruku za poništavanje lozinke"+croatianMessage (IdentifierNotFound ident) = "Korisničko ime/e-pošta nisu pronađeni: " <> ident+croatianMessage Logout = "Odjava"+croatianMessage LogoutTitle = "Odjava"+croatianMessage AuthError = "Pogreška provjere autentičnosti"++danishMessage :: AuthMessage -> Text+danishMessage NoOpenID = "Mangler OpenID identifier"+danishMessage LoginOpenID = "Login med OpenID"+danishMessage LoginGoogle = "Login med Google"+danishMessage LoginYahoo = "Login med Yahoo"+danishMessage Email = "E-mail"+danishMessage UserName = "Brugernavn"+danishMessage Password = "Kodeord"+danishMessage CurrentPassword = "Nuværende kodeord"+danishMessage Register = "Opret"+danishMessage RegisterLong = "Opret en ny konto"+danishMessage EnterEmail = "Indtast din e-mailadresse nedenfor og en bekræftelsesmail vil blive sendt til dig."+danishMessage ConfirmationEmailSentTitle = "Bekræftelsesmail sendt"+danishMessage (ConfirmationEmailSent email) =+    "En bekræftelsesmail er sendt til " `mappend`+    email `mappend`+    "."+danishMessage AddressVerified = "Adresse bekræftet, sæt venligst et nyt kodeord"+danishMessage EmailVerifiedChangePass = "Adresse bekræftet, sæt venligst et nyt kodeord"+danishMessage EmailVerified = "Adresse bekræftet"+danishMessage InvalidKeyTitle = "Ugyldig verifikationsnøgle"+danishMessage InvalidKey = "Beklager, det var en ugyldigt verifikationsnøgle."+danishMessage InvalidEmailPass = "Ugyldigt e-mail/kodeord"+danishMessage BadSetPass = "Du skal være logget ind for at sætte et kodeord"+danishMessage SetPassTitle = "Sæt kodeord"+danishMessage SetPass = "Sæt et nyt kodeord"+danishMessage NewPass = "Nyt kodeord"+danishMessage ConfirmPass = "Bekræft"+danishMessage PassMismatch = "Kodeordne var forskellige, prøv venligst igen"+danishMessage PassUpdated = "Kodeord opdateret"+danishMessage Facebook = "Login med Facebook"+danishMessage LoginViaEmail = "Login med e-mail"+danishMessage InvalidLogin = "Ugyldigt login"+danishMessage NowLoggedIn = "Du er nu logget ind"+danishMessage LoginTitle = "Log ind"+danishMessage PleaseProvideUsername = "Indtast venligst dit brugernavn"+danishMessage PleaseProvidePassword = "Indtasy venligst dit kodeord"+danishMessage NoIdentifierProvided = "Mangler e-mail/username"+danishMessage InvalidEmailAddress = "Ugyldig e-mailadresse indtastet"+danishMessage PasswordResetTitle = "Nulstilning af kodeord"+danishMessage ProvideIdentifier = "E-mail eller brugernavn"+danishMessage SendPasswordResetEmail = "Send kodeordsnulstillingsmail"+danishMessage PasswordResetPrompt = "Indtast din e-mailadresse eller dit brugernavn nedenfor, så bliver en kodeordsnulstilningsmail sendt til dig."+danishMessage InvalidUsernamePass = "Ugyldigt brugernavn/kodeord"+danishMessage (IdentifierNotFound ident) = "Brugernavn findes ikke: " `mappend` ident+danishMessage Logout = "Log ud"+danishMessage LogoutTitle = "Log ud"+danishMessage AuthError = "Fejl ved bekræftelse af identitet"++koreanMessage :: AuthMessage -> Text+koreanMessage NoOpenID = "OpenID ID가 없습니다"+koreanMessage LoginOpenID = "OpenID로 로그인"+koreanMessage LoginGoogle = "Google로 로그인"+koreanMessage LoginYahoo = "Yahoo로 로그인"+koreanMessage Email = "이메일"+koreanMessage UserName = "사용자 이름"+koreanMessage Password = "비밀번호"+koreanMessage CurrentPassword = "현재 비밀번호"+koreanMessage Register = "등록"+koreanMessage RegisterLong = "새 계정 등록"+koreanMessage EnterEmail = "이메일 주소를 아래에 입력하시면 확인 이메일이 발송됩니다."+koreanMessage ConfirmationEmailSentTitle = "확인 이메일을 보냈습니다"+koreanMessage (ConfirmationEmailSent email) =+    "확인 이메일을 " `mappend`+    email `mappend`+    "에 보냈습니다."+koreanMessage AddressVerified = "주소가 인증되었습니다. 새 비밀번호를 설정하세요."+koreanMessage EmailVerifiedChangePass = "주소가 인증되었습니다. 새 비밀번호를 설정하세요."+koreanMessage EmailVerified = "주소가 인증되었습니다"+koreanMessage InvalidKeyTitle = "인증키가 잘못되었습니다"+koreanMessage InvalidKey = "죄송합니다. 잘못된 인증키입니다."+koreanMessage InvalidEmailPass = "이메일 주소나 비밀번호가 잘못되었습니다"+koreanMessage BadSetPass = "비밀번호를 설정하기 위해서는 로그인해야 합니다"+koreanMessage SetPassTitle = "비밀번호 설정"+koreanMessage SetPass = "새 비밀번호 설정"+koreanMessage NewPass = "새 비밀번호"+koreanMessage ConfirmPass = "확인"+koreanMessage PassMismatch = "비밀번호가 맞지 않습니다. 다시 시도해주세요."+koreanMessage PassUpdated = "비밀번호가 업데이트 되었습니다"+koreanMessage Facebook = "Facebook으로 로그인"+koreanMessage LoginViaEmail = "이메일로"+koreanMessage InvalidLogin = "잘못된 로그인입니다"+koreanMessage NowLoggedIn = "로그인했습니다"+koreanMessage LoginTitle = "로그인"+koreanMessage PleaseProvideUsername = "사용자 이름을 입력하세요"+koreanMessage PleaseProvidePassword = "비밀번호를 입력하세요"+koreanMessage NoIdentifierProvided = "이메일 주소나 사용자 이름이 입력되어 있지 않습니다"+koreanMessage InvalidEmailAddress = "이메일 주소가 잘못되었습니다"+koreanMessage PasswordResetTitle = "비밀번호 변경"+koreanMessage ProvideIdentifier = "이메일 주소나 사용자 이름"+koreanMessage SendPasswordResetEmail = "비밀번호 재설정 이메일 보내기"+koreanMessage PasswordResetPrompt = "이메일 주소나 사용자 이름을 아래에 입력하시면 비밀번호 재설정 이메일이 발송됩니다."+koreanMessage InvalidUsernamePass = "사용자 이름이나 비밀번호가 잘못되었습니다"+koreanMessage (IdentifierNotFound ident) = ident `mappend` "는 등록되어 있지 않습니다"+koreanMessage Logout = "로그아웃"+koreanMessage LogoutTitle = "로그아웃"+koreanMessage AuthError = "인증오류"++-- | Romanian translation+--+-- @since 1.6.11.3+romanianMessage :: AuthMessage -> Text+romanianMessage NoOpenID = "Identificatorul OpenID nu a fost găsit"+romanianMessage LoginOpenID = "Conectați-vă cu OpenID"+romanianMessage LoginGoogle = "Conectați-vă cu Google"+romanianMessage LoginYahoo = "Conectați-vă cu Yahoo"+romanianMessage Email = "E-mail"+romanianMessage UserName = "Nume de utilizator"+romanianMessage Password = "Parolă"+romanianMessage CurrentPassword = "Parola curentă"+romanianMessage Register = "Înregistrează-te"+romanianMessage RegisterLong = "Înregistrați un cont nou"+romanianMessage EnterEmail = "Introduceți adresa dvs. de e-mail pentru a primi un e-mail de confirmare."+romanianMessage ConfirmationEmailSentTitle = "Un mesaj de confirmare a fost trimis la adresa dvs. de e-mail"+romanianMessage (ConfirmationEmailSent email) =+    "Un mesaj de confirmare a fost trimis la " `mappend` email `mappend` "."+romanianMessage AddressVerified = "Adresa de e-mail a fost verificată, vă rugăm să setați o parolă nouă"+romanianMessage EmailVerifiedChangePass = "Adresa de e-mail a fost verificată, vă rugăm să setați o parolă nouă"+romanianMessage EmailVerified = "Adresa de e-mail a fost verificată"+romanianMessage InvalidKeyTitle = "Cheie de verificare nevalidă"+romanianMessage InvalidKey = "Cheie de verificare nevalidă."+romanianMessage InvalidEmailPass = "Nume de utilizator și/sau parolă incorect(ă)"+romanianMessage BadSetPass = "Trebuie să fiți autentificat pentru a seta parola"+romanianMessage SetPassTitle = "Setarea parolei"+romanianMessage SetPass = "Setează parolă nouă"+romanianMessage NewPass = "Parolă nouă"+romanianMessage ConfirmPass = "Confirmă"+romanianMessage PassMismatch = "Parolele nu se potrivesc, vă rugăm să încercați din nou"+romanianMessage PassUpdated = "Parola a fost actualizată"+romanianMessage Facebook = "Conectați-vă cu Facebook"+romanianMessage LoginViaEmail = "Conectați-vă cu contul de e-mail"+romanianMessage InvalidLogin = "Nume de utilizator incorect"+romanianMessage NowLoggedIn = "Felicitări. Acum sunteți autentificat."+romanianMessage LoginTitle = "Autentificare"+romanianMessage PleaseProvideUsername = "Introduceți, vă rog, numele dvs. de utilizator"+romanianMessage PleaseProvidePassword = "Introduceți, vă rog, parola dvs."+romanianMessage NoIdentifierProvided = "Adresa de e-mail sau numele de utilizator nu sunt furnizate."+romanianMessage InvalidEmailAddress = "Adresă de e-mail nevalidă"+romanianMessage PasswordResetTitle = "Resetarea parolei"+romanianMessage ProvideIdentifier = "Adresă de e-mail sau nume de utilizator"+romanianMessage SendPasswordResetEmail = "Trimite un e-mail pentru resetarea parolei"+romanianMessage PasswordResetPrompt =+    "Introduceți adresa dvs. de e-mail sau numele de utilizator pentru a primi un e-mail de resetare a parolei."+romanianMessage InvalidUsernamePass = "Nume de utilizator și/sau parolă incorect(ă)"+romanianMessage (IdentifierNotFound ident) = "Numele de utilizator nu a fost găsit: " `mappend` ident+romanianMessage Logout = "Deconectați-vă"+romanianMessage LogoutTitle = "Deconectare"+romanianMessage AuthError = "Eroare de autentificare"
Yesod/Auth/OpenId.hs view
@@ -1,6 +1,9 @@-{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+ module Yesod.Auth.OpenId     ( authOpenId     , forwardUrl@@ -14,36 +17,32 @@ import qualified Web.Authenticate.OpenId as OpenId  import Yesod.Form-import Yesod.Handler-import Yesod.Widget (toWidget, whamlet)-import Yesod.Request-import Text.Cassius (cassius)-#if MIN_VERSION_blaze_html(0, 5, 0)-import Text.Blaze.Html (toHtml)-#else-import Text.Blaze (toHtml)-#endif+import Yesod.Core import Data.Text (Text, isPrefixOf) import qualified Yesod.Auth.Message as Msg-import Control.Exception.Lifted (SomeException, try)+import UnliftIO.Exception (tryAny) import Data.Maybe (fromMaybe)+import qualified Data.Text as T  forwardUrl :: AuthRoute forwardUrl = PluginR "openid" ["forward"]  data IdentifierType = Claimed | OPLocal -authOpenId :: YesodAuth m+authOpenId :: YesodAuth master            => IdentifierType            -> [(Text, Text)] -- ^ extension fields-           -> AuthPlugin m+           -> AuthPlugin master authOpenId idType extensionFields =     AuthPlugin "openid" dispatch login   where     complete = PluginR "openid" ["complete"]++    name :: Text     name = "openid_identifier"+     login tm = do-        ident <- lift newIdent+        ident <- newIdent         -- FIXME this is a hack to get GHC 7.6's type checker to allow the         -- code, but it shouldn't be necessary         let y :: a -> [(Text, Text)] -> Text@@ -55,9 +54,6 @@         [whamlet| $newline never <form method="get" action="@{tm forwardUrl}">-    <input type="hidden" name="openid_identifier" value="https://www.google.com/accounts/o8/id">-    <button .openid-google>_{Msg.LoginGoogle}-<form method="get" action="@{tm forwardUrl}">     <input type="hidden" name="openid_identifier" value="http://me.yahoo.com">     <button .openid-yahoo>_{Msg.LoginYahoo} <form method="get" action="@{tm forwardUrl}">@@ -65,24 +61,21 @@     <input id="#{ident}" type="text" name="#{name}" value="http://">     <input type="submit" value="_{Msg.LoginOpenID}"> |]++    dispatch :: Text -> [Text] -> AuthHandler master TypedContent     dispatch "GET" ["forward"] = do         roid <- runInputGet $ iopt textField name         case roid of             Just oid -> do+                tm <- getRouteToParent                 render <- getUrlRender-                toMaster <- getRouteToMaster-                let complete' = render $ toMaster complete-                master <- getYesod-                eres <- lift $ try $ OpenId.getForwardUrl oid complete' Nothing extensionFields (authHttpManager master)+                let complete' = render $ tm complete+                manager <- authHttpManager+                eres <- tryAny $ OpenId.getForwardUrl oid complete' Nothing extensionFields manager                 case eres of-                    Left err -> do-                        setMessage $ toHtml $ show (err :: SomeException)-                        redirect $ toMaster LoginR+                    Left err -> loginErrorMessage (tm LoginR) $ T.pack $ show err                     Right x -> redirect x-            Nothing -> do-                toMaster <- getRouteToMaster-                setMessageI Msg.NoOpenID-                redirect $ toMaster LoginR+            Nothing -> loginErrorMessageI LoginR Msg.NoOpenID     dispatch "GET" ["complete", ""] = dispatch "GET" ["complete"] -- compatibility issues     dispatch "GET" ["complete"] = do         rr <- getRequest@@ -93,29 +86,29 @@         completeHelper idType posts     dispatch _ _ = notFound -completeHelper :: YesodAuth m => IdentifierType -> [(Text, Text)] -> GHandler Auth m ()+completeHelper :: IdentifierType -> [(Text, Text)] -> AuthHandler master TypedContent completeHelper idType gets' = do-        master <- getYesod-        eres <- lift $ try $ OpenId.authenticateClaimed gets' (authHttpManager master)-        toMaster <- getRouteToMaster-        let onFailure err = do-            setMessage $ toHtml $ show (err :: SomeException)-            redirect $ toMaster LoginR-        let onSuccess oir = do-                let claimed =-                        case OpenId.oirClaimed oir of-                            Nothing -> id-                            Just (OpenId.Identifier i') -> ((claimedKey, i'):)-                    oplocal =-                        case OpenId.oirOpLocal oir of-                            OpenId.Identifier i' -> ((opLocalKey, i'):)-                    gets'' = oplocal $ claimed $ filter (\(k, _) -> not $ "__" `isPrefixOf` k) gets'-                    i = OpenId.identifier $-                            case idType of-                                OPLocal -> OpenId.oirOpLocal oir-                                Claimed -> fromMaybe (OpenId.oirOpLocal oir) $ OpenId.oirClaimed oir-                setCreds True $ Creds "openid" i gets''-        either onFailure onSuccess eres+    manager <- authHttpManager+    eres <- tryAny $ OpenId.authenticateClaimed gets' manager+    either onFailure onSuccess eres+  where+    onFailure err = do+        tm <- getRouteToParent+        loginErrorMessage (tm LoginR) $ T.pack $ show err+    onSuccess oir = do+            let claimed =+                    case OpenId.oirClaimed oir of+                        Nothing -> id+                        Just (OpenId.Identifier i') -> ((claimedKey, i'):)+                oplocal =+                    case OpenId.oirOpLocal oir of+                        OpenId.Identifier i' -> ((opLocalKey, i'):)+                gets'' = oplocal $ claimed $ filter (\(k, _) -> not $ "__" `isPrefixOf` k) gets'+                i = OpenId.identifier $+                        case idType of+                            OPLocal -> OpenId.oirOpLocal oir+                            Claimed -> fromMaybe (OpenId.oirOpLocal oir) $ OpenId.oirClaimed oir+            setCredsRedirect $ Creds "openid" i gets''  -- | The main identifier provided by the OpenID authentication plugin is the -- \"OP-local identifier\". There is also sometimes a \"claimed\" identifier
+ Yesod/Auth/Routes.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ViewPatterns #-}++module Yesod.Auth.Routes where++import Yesod.Core+import Data.Text (Text)++data Auth = Auth++mkYesodSubData "Auth" [parseRoutes|+/check                 CheckR      GET+/login                 LoginR      GET+/logout                LogoutR     GET POST+/page/#Text/*Texts     PluginR+|]
Yesod/Auth/Rpxnow.hs view
@@ -1,7 +1,9 @@-{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RankNTypes #-}-{-# LANGUAGE FlexibleContexts #-}+ module Yesod.Auth.Rpxnow     ( authRpxnow     ) where@@ -10,43 +12,38 @@ import qualified Web.Authenticate.Rpxnow as Rpxnow import Control.Monad (mplus) -import Yesod.Handler-import Yesod.Widget-import Yesod.Request-import Text.Hamlet (hamlet)+import Yesod.Core import Data.Text (pack, unpack) import Data.Text.Encoding (encodeUtf8, decodeUtf8With) import Data.Text.Encoding.Error (lenientDecode) import Control.Arrow ((***)) import Network.HTTP.Types (renderQuery) -authRpxnow :: YesodAuth m+authRpxnow :: YesodAuth master            => String -- ^ app name            -> String -- ^ key-           -> AuthPlugin m+           -> AuthPlugin master authRpxnow app apiKey =     AuthPlugin "rpxnow" dispatch login   where-    login ::-        forall sub master.-        ToWidget sub master (GWidget sub master ())-        => (Route Auth -> Route master) -> GWidget sub master ()     login tm = do-        render <- lift getUrlRender+        render <- getUrlRender         let queryString = decodeUtf8With lenientDecode                         $ renderQuery True [("token_url", Just $ encodeUtf8 $ render $ tm $ PluginR "rpxnow" [])]         toWidget [hamlet| $newline never <iframe src="http://#{app}.rpxnow.com/openid/embed#{queryString}" scrolling="no" frameBorder="no" allowtransparency="true" style="width:400px;height:240px"> |]++    dispatch :: a -> [b] -> AuthHandler master TypedContent     dispatch _ [] = do         token1 <- lookupGetParams "token"         token2 <- lookupPostParams "token"         token <- case token1 ++ token2 of                         [] -> invalidArgs ["token: Value not supplied"]                         x:_ -> return $ unpack x-        master <- getYesod-        Rpxnow.Identifier ident extra <- lift $ Rpxnow.authenticate apiKey token (authHttpManager master)+        manager <- authHttpManager+        Rpxnow.Identifier ident extra <- Rpxnow.authenticate apiKey token manager         let creds =                 Creds "rpxnow" ident                 $ maybe id (\x -> (:) ("verifiedEmail", x))@@ -54,7 +51,7 @@                 $ maybe id (\x -> (:) ("displayName", x))                     (fmap pack $ getDisplayName $ map (unpack *** unpack) extra)                   []-        setCreds True creds+        setCredsRedirect creds     dispatch _ _ = notFound  -- | Get some form of a display name.
+ Yesod/Auth/Util/PasswordStore.hs view
@@ -0,0 +1,457 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}++-- |+-- This is a fork of pwstore-fast, originally copyright (c) Peter Scott, 2011,+-- and released under a BSD-style licence.+--+-- Securely store hashed, salted passwords. If you need to store and verify+-- passwords, there are many wrong ways to do it, most of them all too+-- common. Some people store users' passwords in plain text. Then, when an+-- attacker manages to get their hands on this file, they have the passwords for+-- every user's account. One step up, but still wrong, is to simply hash all+-- passwords with SHA1 or something. This is vulnerable to rainbow table and+-- dictionary attacks. One step up from that is to hash the password along with+-- a unique salt value. This is vulnerable to dictionary attacks, since guessing+-- a password is very fast. The right thing to do is to use a slow hash+-- function, to add some small but significant delay, that will be negligible+-- for legitimate users but prohibitively expensive for someone trying to guess+-- passwords by brute force. That is what this library does. It iterates a+-- SHA256 hash, with a random salt, a few thousand times. This scheme is known+-- as PBKDF1, and is generally considered secure; there is nothing innovative+-- happening here.+--+-- The API here is very simple. What you store are called /password hashes/.+-- They are strings (technically, ByteStrings) that look like this:+--+-- > "sha256|14|jEWU94phx4QzNyH94Qp4CQ==|5GEw+jxP/4WLgzt9VS3Ee3nhqBlDsrKiB+rq7JfMckU="+--+-- Each password hash shows the algorithm, the strength (more on that later),+-- the salt, and the hashed-and-salted password. You store these on your server,+-- in a database, for when you need to verify a password. You make a password+-- hash with the 'makePassword' function. Here's an example:+--+-- > >>> makePassword "hunter2" 14+-- > "sha256|14|Zo4LdZGrv/HYNAUG3q8WcA==|zKjbHZoTpuPLp1lh6ATolWGIKjhXvY4TysuKvqtNFyk="+--+-- This will hash the password @\"hunter2\"@, with strength 14, which is a good+-- default value. The strength here determines how long the hashing will+-- take. When doing the hashing, we iterate the SHA256 hash function+-- @2^strength@ times, so increasing the strength by 1 makes the hashing take+-- twice as long. When computers get faster, you can bump up the strength a+-- little bit to compensate. You can strengthen existing password hashes with+-- the 'strengthenPassword' function. Note that 'makePassword' needs to generate+-- random numbers, so its return type is 'IO' 'ByteString'. If you want to avoid+-- the 'IO' monad, you can generate your own salt and pass it to+-- 'makePasswordSalt'.+--+-- Your strength value should not be less than 12, and 14 is a good default+-- value at the time of this writing, in 2013.+--+-- Once you've got your password hashes, the second big thing you need to do+-- with them is verify passwords against them. When a user gives you a password,+-- you compare it with a password hash using the 'verifyPassword' function:+--+-- > >>> verifyPassword "wrong guess" passwordHash+-- > False+-- > >>> verifyPassword "hunter2" passwordHash+-- > True+--+-- These two functions are really all you need. If you want to make existing+-- password hashes stronger, you can use 'strengthenPassword'. Just pass it an+-- existing password hash and a new strength value, and it will return a new+-- password hash with that strength value, which will match the same password as+-- the old password hash.+--+-- Note that, as of version 2.4, you can also use PBKDF2, and specify the exact+-- iteration count. This does not have a significant effect on security, but can+-- be handy for compatibility with other code.+--+-- @since 1.4.18++module Yesod.Auth.Util.PasswordStore (++        -- * Algorithms+        pbkdf1,                 -- :: ByteString -> Salt -> Int -> ByteString+        pbkdf2,                 -- :: ByteString -> Salt -> Int -> ByteString++        -- * Registering and verifying passwords+        makePassword,           -- :: ByteString -> Int -> IO ByteString+        makePasswordWith,       -- :: (ByteString -> Salt -> Int -> ByteString) ->+                                --    ByteString -> Int -> IO ByteString+        makePasswordSalt,       -- :: ByteString -> ByteString -> Int -> ByteString+        makePasswordSaltWith,   -- :: (ByteString -> Salt -> Int -> ByteString) ->+                                --    ByteString -> Salt -> Int -> ByteString+        verifyPassword,         -- :: ByteString -> ByteString -> Bool+        verifyPasswordWith,     -- :: (ByteString -> Salt -> Int -> ByteString) ->+                                --    (Int -> Int) -> ByteString -> ByteString -> Bool++        -- * Updating password hash strength+        strengthenPassword,     -- :: ByteString -> Int -> ByteString+        passwordStrength,       -- :: ByteString -> Int++        -- * Utilities+        Salt,+        isPasswordFormatValid,  -- :: ByteString -> Bool+        genSaltIO,              -- :: IO Salt+        genSaltRandom,          -- :: (RandomGen b) => b -> (Salt, b)+        makeSalt,               -- :: ByteString -> Salt+        exportSalt,             -- :: Salt -> ByteString+        importSalt              -- :: ByteString -> Salt+  ) where++import qualified Crypto.MAC.HMAC as CH+import qualified Crypto.Hash as CH+import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import qualified Data.Binary as Binary+import Control.Monad+import Control.Monad.ST+import Data.STRef+import Data.Bits+import Data.ByteString.Char8 (ByteString)+import Data.ByteString.Base64 (encode, decodeLenient)+import System.IO+import System.Random+import Data.Maybe+import qualified Control.Exception+import Data.ByteArray (convert)++---------------------+-- Cryptographic base+---------------------++-- | PBKDF1 key-derivation function. Takes a password, a 'Salt', and a number of+-- iterations. The number of iterations should be at least 1000, and probably+-- more. 5000 is a reasonable number, computing almost instantaneously. This+-- will give a 32-byte 'ByteString' as output. Both the salt and this 32-byte+-- key should be stored in the password file. When a user wishes to authenticate+-- a password, just pass it and the salt to this function, and see if the output+-- matches.+--+-- @since 1.4.18+--+pbkdf1 :: ByteString -> Salt -> Int -> ByteString+pbkdf1 password (SaltBS salt) iter = hashRounds first_hash (iter + 1)+  where+    first_hash =+      convert $+      ((CH.hashFinalize $ CH.hashInit `CH.hashUpdate` password `CH.hashUpdate` salt) :: CH.Digest CH.SHA256)+++-- | Hash a 'ByteString' for a given number of rounds. The number of rounds is 0+-- or more. If the number of rounds specified is 0, the ByteString will be+-- returned unmodified.+hashRounds :: ByteString -> Int -> ByteString+hashRounds (!bs) 0 = bs+hashRounds bs rounds = hashRounds (convert (CH.hash bs :: CH.Digest CH.SHA256)) (rounds - 1)++-- | Computes the hmacSHA256 of the given message, with the given 'Salt'.+hmacSHA256 :: ByteString+           -- ^ The secret (the salt)+           -> ByteString+           -- ^ The clear-text message+           -> ByteString+           -- ^ The encoded message+hmacSHA256 secret msg =+    convert (CH.hmacGetDigest (CH.hmac secret msg) :: CH.Digest CH.SHA256)++-- | PBKDF2 key-derivation function.+-- For details see @http://tools.ietf.org/html/rfc2898@.+-- @32@ is the most common digest size for @SHA256@, and is+-- what the algorithm internally uses.+-- @HMAC+SHA256@ is used as @PRF@, because @HMAC+SHA1@ is considered too weak.+--+-- @since 1.4.18+--+pbkdf2 :: ByteString -> Salt -> Int -> ByteString+pbkdf2 password (SaltBS salt) c =+    let hLen = 32+        dkLen = hLen in go hLen dkLen+  where+    go hLen dkLen | dkLen > (2 ^ (32 :: Int) - 1) * hLen = error "Derived key too long."+                  | otherwise =+                      let !l = ceiling ((fromIntegral dkLen / fromIntegral hLen) :: Double)+                          !r = dkLen - (l - 1) * hLen+                          chunks = [f i | i <- [1 .. l]]+                      in (B.concat . init $ chunks) `B.append` B.take r (last chunks)++    -- The @f@ function, as defined in the spec.+    -- It calls 'u' under the hood.+    f :: Int -> ByteString+    f i = let !u1 = hmacSHA256 password (salt `B.append` int i)+      -- Using the ST Monad, for maximum performance.+      in runST $ do+          u <- newSTRef u1+          accum <- newSTRef u1+          forM_ [2 .. c] $ \_ -> do+            modifySTRef' u (hmacSHA256 password)+            currentU <- readSTRef u+            modifySTRef' accum (`xor'` currentU)+          readSTRef accum++    -- int(i), as defined in the spec.+    int :: Int -> ByteString+    int i = let str = BL.unpack . Binary.encode $ i+            in BS.pack $ drop (length str - 4) str++    -- | A convenience function to XOR two 'ByteString' together.+    xor' :: ByteString -> ByteString -> ByteString+    xor' !b1 !b2 = BS.pack $ BS.zipWith xor b1 b2++-- | Generate a 'Salt' from 128 bits of data from @\/dev\/urandom@, with the+-- system RNG as a fallback. This is the function used to generate salts by+-- 'makePassword'.+--+-- @since 1.4.18+--+genSaltIO :: IO Salt+genSaltIO =+    Control.Exception.catch genSaltDevURandom def+  where+    def :: IOError -> IO Salt+    def _ = genSaltSysRandom++-- | Generate a 'Salt' from @\/dev\/urandom@.+genSaltDevURandom :: IO Salt+genSaltDevURandom = withFile "/dev/urandom" ReadMode $ \h -> do+                      rawSalt <- B.hGet h 16+                      return $ makeSalt rawSalt++-- | Generate a 'Salt' from 'System.Random'.+genSaltSysRandom :: IO Salt+genSaltSysRandom = randomChars >>= return . makeSalt . B.pack+    where randomChars = sequence $ replicate 16 $ randomRIO ('\NUL', '\255')++-----------------------+-- Password hash format+-----------------------++-- Format: "sha256|strength|salt|hash", where strength is an unsigned int, salt+-- is a base64-encoded 16-byte random number, and hash is a base64-encoded hash+-- value.++-- | Try to parse a password hash.+readPwHash :: ByteString -> Maybe (Int, Salt, ByteString)+readPwHash pw+    | ["sha256", strBS, salt, hash] <- broken+    , B.length hash == 44 =+        (\(strength, _) -> (strength, SaltBS salt, hash))+            <$> B.readInt strBS+    | otherwise = Nothing+  where+    broken = B.split '|' pw+++-- | Encode a password hash, from a @(strength, salt, hash)@ tuple, where+-- strength is an 'Int', and both @salt@ and @hash@ are base64-encoded+-- 'ByteString's.+writePwHash :: (Int, Salt, ByteString) -> ByteString+writePwHash (strength, SaltBS salt, hash) =+    B.intercalate "|" ["sha256", B.pack (show strength), salt, hash]++-----------------+-- High level API+-----------------++-- | Hash a password with a given strength (14 is a good default). The output of+-- this function can be written directly to a password file or+-- database. Generates a salt using high-quality randomness from+-- @\/dev\/urandom@ or (if that is not available, for example on Windows)+-- 'System.Random', which is included in the hashed output.+--+-- @since 1.4.18+--+makePassword :: ByteString -> Int -> IO ByteString+makePassword = makePasswordWith pbkdf1++-- | A generic version of 'makePassword', which allow the user+-- to choose the algorithm to use.+--+-- >>> makePasswordWith pbkdf1 "password" 14+--+-- @since 1.4.18+--+makePasswordWith :: (ByteString -> Salt -> Int -> ByteString)+                 -- ^ The algorithm to use (e.g. pbkdf1)+                 -> ByteString+                 -- ^ The password to encrypt+                 -> Int+                 -- ^ log2 of the number of iterations+                 -> IO ByteString+makePasswordWith algorithm password strength = do+  salt <- genSaltIO+  return $ makePasswordSaltWith algorithm (2 ^) password salt strength++-- | A generic version of 'makePasswordSalt', meant to give the user+-- the maximum control over the generation parameters.+-- Note that, unlike 'makePasswordWith', this function takes the @raw@+-- number of iterations. This means the user will need to specify a+-- sensible value, typically @10000@ or @20000@.+--+-- @since 1.4.18+--+makePasswordSaltWith :: (ByteString -> Salt -> Int -> ByteString)+                     -- ^ A function modeling an algorithm (e.g. 'pbkdf1')+                     -> (Int -> Int)+                     -- ^ A function to modify the strength+                     -> ByteString+                     -- ^ A password, given as clear text+                     -> Salt+                     -- ^ A hash 'Salt'+                     -> Int+                     -- ^ The password strength (e.g. @10000, 20000, etc.@)+                     -> ByteString+makePasswordSaltWith algorithm strengthModifier pwd salt strength = writePwHash (strength, salt, hash)+    where hash = encode $ algorithm pwd salt (strengthModifier strength)++-- | Hash a password with a given strength (14 is a good default), using a given+-- salt. The output of this function can be written directly to a password file+-- or database. Example:+--+-- > >>> makePasswordSalt "hunter2" (makeSalt "72cd18b5ebfe6e96") 14+-- > "sha256|14|NzJjZDE4YjVlYmZlNmU5Ng==|yuiNrZW3KHX+pd0sWy9NTTsy5Yopmtx4UYscItSsoxc="+--+-- @since 1.4.18+--+makePasswordSalt :: ByteString -> Salt -> Int -> ByteString+makePasswordSalt = makePasswordSaltWith pbkdf1 (2 ^)++-- | 'verifyPasswordWith' @algorithm userInput pwHash@ verifies+-- the password @userInput@ given by the user against the stored password+-- hash @pwHash@, with the hashing algorithm @algorithm@.  Returns 'True' if the+-- given password is correct, and 'False' if it is not.+-- This function allows the programmer to specify the algorithm to use,+-- e.g. 'pbkdf1' or 'pbkdf2'.+-- Note: If you want to verify a password previously generated with+-- 'makePasswordSaltWith', but without modifying the number of iterations,+-- you can do:+--+-- > >>> verifyPasswordWith pbkdf2 id "hunter2" "sha256..."+-- > True+--+-- @since 1.4.18+--+verifyPasswordWith :: (ByteString -> Salt -> Int -> ByteString)+                   -- ^ A function modeling an algorithm (e.g. pbkdf1)+                   -> (Int -> Int)+                   -- ^ A function to modify the strength+                   -> ByteString+                   -- ^ User password+                   -> ByteString+                   -- ^ The generated hash (e.g. sha256|14...)+                   -> Bool+verifyPasswordWith algorithm strengthModifier userInput pwHash =+    case readPwHash pwHash of+      Nothing -> False+      Just (strength, salt, goodHash) ->+          encode (algorithm userInput salt (strengthModifier strength)) == goodHash++-- | Like 'verifyPasswordWith', but uses 'pbkdf1' as algorithm.+--+-- @since 1.4.18+--+verifyPassword :: ByteString -> ByteString -> Bool+verifyPassword = verifyPasswordWith pbkdf1 (2 ^)++-- | Try to strengthen a password hash, by hashing it some more+-- times. @'strengthenPassword' pwHash new_strength@ will return a new password+-- hash with strength at least @new_strength@. If the password hash already has+-- strength greater than or equal to @new_strength@, then it is returned+-- unmodified. If the password hash is invalid and does not parse, it will be+-- returned without comment.+--+-- This function can be used to periodically update your password database when+-- computers get faster, in order to keep up with Moore's law. This isn't hugely+-- important, but it's a good idea.+--+-- @since 1.4.18+--+strengthenPassword :: ByteString -> Int -> ByteString+strengthenPassword pwHash newstr =+    case readPwHash pwHash of+      Nothing -> pwHash+      Just (oldstr, salt, hashB64) ->+          if oldstr < newstr then+              writePwHash (newstr, salt, newHash)+          else+              pwHash+          where newHash = encode $ hashRounds hash extraRounds+                extraRounds = (2 ^ newstr) - (2 ^ oldstr)+                hash = decodeLenient hashB64++-- | Return the strength of a password hash.+--+-- @since 1.4.18+--+passwordStrength :: ByteString -> Int+passwordStrength pwHash = case readPwHash pwHash of+                            Nothing               -> 0+                            Just (strength, _, _) -> strength++------------+-- Utilities+------------++-- | A salt is a unique random value which is stored as part of the password+-- hash. You can generate a salt with 'genSaltIO' or 'genSaltRandom', or if you+-- really know what you're doing, you can create them from your own ByteString+-- values with 'makeSalt'.+--+-- @since 1.4.18+--+newtype Salt = SaltBS ByteString+    deriving (Show, Eq, Ord)++-- | Create a 'Salt' from a 'ByteString'. The input must be at least 8+-- characters, and can contain arbitrary bytes. Most users will not need to use+-- this function.+--+-- @since 1.4.18+--+makeSalt :: ByteString -> Salt+makeSalt = SaltBS . encode . check_length+    where check_length salt | B.length salt < 8 =+                                error "Salt too short. Minimum length is 8 characters."+                            | otherwise = salt++-- | Convert a 'Salt' into a 'ByteString'. The resulting 'ByteString' will be+-- base64-encoded. Most users will not need to use this function.+--+-- @since 1.4.18+--+exportSalt :: Salt -> ByteString+exportSalt (SaltBS bs) = bs++-- | Convert a raw 'ByteString' into a 'Salt'.+-- Use this function with caution, since using a weak salt will result in a+-- weak password.+--+-- @since 1.4.18+--+importSalt :: ByteString -> Salt+importSalt = SaltBS++-- | Is the format of a password hash valid? Attempts to parse a given password+-- hash. Returns 'True' if it parses correctly, and 'False' otherwise.+--+-- @since 1.4.18+--+isPasswordFormatValid :: ByteString -> Bool+isPasswordFormatValid = isJust . readPwHash++-- | Generate a 'Salt' with 128 bits of data taken from a given random number+-- generator. Returns the salt and the updated random number generator. This is+-- meant to be used with 'makePasswordSalt' by people who would prefer to either+-- use their own random number generator or avoid the 'IO' monad.+--+-- @since 1.4.18+--+genSaltRandom :: (RandomGen b) => b -> (Salt, b)+genSaltRandom gen = (salt, newgen)+    where rands _ 0 = []+          rands g n = (a, g') : rands g' (n-1 :: Int)+              where (a, g') = randomR ('\NUL', '\255') g+          salt   = makeSalt $ B.pack $ map fst (rands gen 16)+          newgen = snd $ last (rands gen 16)
yesod-auth.cabal view
@@ -1,5 +1,6 @@+cabal-version:   >=1.10 name:            yesod-auth-version:         1.1.7+version:         1.6.12.1 license:         MIT license-file:    LICENSE author:          Michael Snoyman, Patrick Brisbin@@ -7,54 +8,73 @@ synopsis:        Authentication for Yesod. category:        Web, Yesod stability:       Stable-cabal-version:   >= 1.6.0 build-type:      Simple homepage:        http://www.yesodweb.com/-description:     Authentication for Yesod.+description:     API docs and the README are available at <http://www.stackage.org/package/yesod-auth> extra-source-files: persona_sign_in_blue.png+                    README.md+                    ChangeLog.md +flag network-uri+  description: Get Network.URI from the network-uri package+  default: True+ library-    build-depends:   base                    >= 4         && < 5-                   , authenticate            >= 1.3+    default-language: Haskell2010+    build-depends:   base                    >= 4.11      && < 5+                   , aeson                   >= 0.7+                   , attoparsec-aeson        >= 2.1+                   , authenticate            >= 1.3.4+                   , base16-bytestring+                   , base64-bytestring+                   , binary+                   , blaze-builder+                   , blaze-html              >= 0.5+                   , blaze-markup            >= 0.5.1                    , bytestring              >= 0.9.1.4-                   , yesod-core              >= 1.1       && < 1.2-                   , wai                     >= 1.3-                   , template-haskell-                   , pureMD5                 >= 2.0+                   , conduit                 >= 1.3+                   , conduit-extra+                   , containers+                   , crypton+                   , data-default+                   , email-validate          >= 1.0+                   , file-embed+                   , http-client             >= 0.5+                   , http-client-tls+                   , http-conduit            >= 2.1+                   , http-types+                   , memory+                   , nonce                   >= 1.0.2     && < 1.1+                   , persistent              >= 2.8                    , random                  >= 1.0.0.2+                   , safe+                   , shakespeare+                   , template-haskell                    , text                    >= 0.7-                   , mime-mail               >= 0.3-                   , yesod-persistent        >= 1.1-                   , hamlet                  >= 1.1       && < 1.2-                   , shakespeare-css         >= 1.0       && < 1.1-                   , shakespeare-js          >= 1.0.2     && < 1.2-                   , yesod-json              >= 1.1       && < 1.2-                   , containers-                   , unordered-containers-                   , yesod-form              >= 1.1       && < 1.3+                   , time                    , transformers            >= 0.2.2-                   , persistent              >= 1.0       && < 1.2-                   , persistent-template     >= 1.0       && < 1.2-                   , SHA                     >= 1.4.1.3-                   , http-conduit            >= 1.5-                   , aeson                   >= 0.5-                   , pwstore-fast            >= 2.2-                   , lifted-base             >= 0.1-                   , blaze-html              >= 0.5-                   , blaze-markup            >= 0.5.1-                   , network-                   , http-types-                   , file-embed+                   , unliftio+                   , unliftio-core+                   , unordered-containers+                   , wai                     >= 1.4+                   , yesod-core              >= 1.6       && < 1.8+                   , yesod-form              >= 1.6       && < 1.8+                   , yesod-persistent        >= 1.6 +    if flag(network-uri)+      build-depends: network-uri >= 2.6+     exposed-modules: Yesod.Auth                      Yesod.Auth.BrowserId                      Yesod.Auth.Dummy                      Yesod.Auth.Email                      Yesod.Auth.OpenId                      Yesod.Auth.Rpxnow-                     Yesod.Auth.HashDB                      Yesod.Auth.Message-                     Yesod.Auth.GoogleEmail+                     Yesod.Auth.GoogleEmail2+                     Yesod.Auth.Hardcoded+                     Yesod.Auth.Util.PasswordStore+    other-modules:   Yesod.Auth.Routes     ghc-options:     -Wall  source-repository head