packages feed

yesod-auth 1.3.0.4 → 1.3.0.5

raw patch · 4 files changed

+448/−14 lines, 4 filesdep +base64-bytestringdep +binarydep +byteabledep −pwstore-fastPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: base64-bytestring, binary, byteable

Dependencies removed: pwstore-fast

API changes (from Hackage documentation)

- Yesod.Auth.Email: class (YesodAuth site, PathPiece (AuthEmailId site), RenderMessage site AuthMessage) => YesodAuthEmail site where type family AuthEmailId site randomKey _ = do { stdgen <- newStdGen; return $ pack $ fst $ randomString 10 stdgen } needOldPassword aid' = do { mkey <- lookupSession loginLinkKey; case mkey >>= readMay . 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 } } checkPasswordSecurity _ x | length x >= 3 = return $ Right () | otherwise = return $ Left "Password must be at least three characters" confirmationEmailSentResponse identifier = do { mr <- getMessageRender; messageJson401 (mr msg) $ authLayout $ do { setTitleI ConfirmationEmailSentTitle; do { (asWidgetT . toWidget) ((preEscapedText . pack) "<p>"); ((liftM (toHtml .) getMessageRender) >>= (\ urender_akwh -> (asWidgetT . toWidget) (urender_akwh msg))); (asWidgetT . toWidget) ((preEscapedText . pack) "</p>") } } } where msg = ConfirmationEmailSent identifier normalizeEmailAddress _ = toLower registerHandler = defaultRegisterHandler forgotPasswordHandler = defaultForgotPasswordHandler setPasswordHandler = defaultSetPasswordHandler
+ Yesod.Auth.Email: class (YesodAuth site, PathPiece (AuthEmailId site), RenderMessage site AuthMessage) => YesodAuthEmail site where type family AuthEmailId site randomKey _ = do { stdgen <- newStdGen; return $ pack $ fst $ randomString 10 stdgen } needOldPassword aid' = do { mkey <- lookupSession loginLinkKey; case mkey >>= readMay . 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 } } checkPasswordSecurity _ x | length x >= 3 = return $ Right () | otherwise = return $ Left "Password must be at least three characters" confirmationEmailSentResponse identifier = do { mr <- getMessageRender; messageJson401 (mr msg) $ authLayout $ do { setTitleI ConfirmationEmailSentTitle; do { (asWidgetT . toWidget) ((preEscapedText . pack) "<p>"); ((liftM (toHtml .) getMessageRender) >>= (\ urender_ambB -> (asWidgetT . toWidget) (urender_ambB msg))); (asWidgetT . toWidget) ((preEscapedText . pack) "</p>") } } } where msg = ConfirmationEmailSent identifier normalizeEmailAddress _ = toLower registerHandler = defaultRegisterHandler forgotPasswordHandler = defaultForgotPasswordHandler setPasswordHandler = defaultSetPasswordHandler

Files

Yesod/Auth/Email.hs view
@@ -45,7 +45,7 @@ import Data.Text.Encoding.Error (lenientDecode) import Data.Text (Text) import Yesod.Core-import qualified Crypto.PasswordStore as PS+import qualified Yesod.PasswordStore as PS import qualified Text.Email.Validate import qualified Yesod.Auth.Message as Msg import Control.Applicative ((<$>), (<*>))@@ -250,8 +250,11 @@                 <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+                <button type=submit .btn .btn-success>+                    _{Msg.LoginViaEmail}+                &nbsp;+                <a href="@{tm registerR}" .btn .btn-default>+                    _{Msg.RegisterLong} |]   where     dispatch "GET" ["register"] = getRegisterR >>= sendResponse
Yesod/Auth/Message.hs view
@@ -311,15 +311,14 @@ frenchMessage LoginTitle = "Se connecter" frenchMessage PleaseProvideUsername = "Merci de renseigner votre nom d'utilisateur" frenchMessage PleaseProvidePassword = "Merci de spécifier un mot de passe"-frenchMessage NoIdentifierProvided = "No email/username provided"-frenchMessage InvalidEmailAddress = "Invalid email address provided"-frenchMessage PasswordResetTitle = "Password Reset"-frenchMessage ProvideIdentifier = "Email or Username"-frenchMessage SendPasswordResetEmail = "Send password reset email"-frenchMessage PasswordResetPrompt = "Enter your e-mail address or username below, and a password reset e-mail will be sent to you."-frenchMessage InvalidUsernamePass = "Invalid username/password combination"--- TODO-frenchMessage i@(IdentifierNotFound _) = englishMessage i+frenchMessage NoIdentifierProvided = "Adresse électronique/nom d'utilisateur non spécifié"+frenchMessage InvalidEmailAddress = "Adresse électronique spécifiée invalide"+frenchMessage PasswordResetTitle = "Réinitialisation de mot de passe"+frenchMessage ProvideIdentifier = "Adresse électronique ou nom d'utilisateur"+frenchMessage SendPasswordResetEmail = "Envoie d'un message électronique pour Réinitialisation le mot de passe"+frenchMessage PasswordResetPrompt = "Entrez votre adresse électronique ou votre nom d'utilisateur ci-dessous, et un message électronique de réinitialisation de mot de passe vous sera envoyé."+frenchMessage InvalidUsernamePass = "Le couble nom d'utilisateur/mot de passe invalide"+frenchMessage (IdentifierNotFound ident) = "Nom d'utilisateur introuvable: " `mappend` ident  norwegianBokmålMessage :: AuthMessage -> Text norwegianBokmålMessage NoOpenID = "Ingen OpenID-identifiserer funnet"
+ Yesod/PasswordStore.hs view
@@ -0,0 +1,429 @@+{-# LANGUAGE OverloadedStrings, BangPatterns #-}+{-# LANGUAGE CPP #-}+-- |+-- Module      : Crypto.PasswordStore+-- Copyright   : (c) Peter Scott, 2011+-- License     : BSD-style+--+-- Maintainer  : pjscott@iastate.edu+-- Stability   : experimental+-- Portability : portable+--+-- 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 12, 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.++module Yesod.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.Hash as CH+import qualified Crypto.Hash.SHA256 as H+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.Byteable (toBytes)+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++---------------------+-- 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.+pbkdf1 :: ByteString -> Salt -> Int -> ByteString+pbkdf1 password (SaltBS salt) iter = hashRounds first_hash (iter + 1)+    where first_hash = H.finalize $ H.init `H.update` password `H.update` salt++-- | 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 (H.hash bs) (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 =+    toBytes (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.+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 - 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'.+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 | length broken /= 4+                || algorithm /= "sha256"+                || B.length hash /= 44 = Nothing+              | otherwise = case B.readInt strBS of+                              Just (strength, _) -> Just (strength, SaltBS salt, hash)+                              Nothing -> Nothing+    where broken = B.split '|' pw+          [algorithm, strBS, salt, hash] = broken++-- | 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.+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+--+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@.+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="+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+--+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.+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.+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.+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'.+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.+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.+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.+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.+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.+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)++#if !MIN_VERSION_base(4, 6, 0)+-- | Strict version of 'modifySTRef'+modifySTRef' :: STRef s a -> (a -> a) -> ST s ()+modifySTRef' ref f = do+    x <- readSTRef ref+    let x' = f x+    x' `seq` writeSTRef ref x'+#endif++#if MIN_VERSION_bytestring(0, 10, 0)+toStrict :: BL.ByteString -> BS.ByteString+toStrict = BL.toStrict++fromStrict :: BS.ByteString -> BL.ByteString+fromStrict = BL.fromStrict+#else+toStrict :: BL.ByteString -> BS.ByteString+toStrict = BS.concat . BL.toChunks++fromStrict :: BS.ByteString -> BL.ByteString+fromStrict = BL.fromChunks . return+#endif
yesod-auth.cabal view
@@ -1,5 +1,5 @@ name:            yesod-auth-version:         1.3.0.4+version:         1.3.0.5 license:         MIT license-file:    LICENSE author:          Michael Snoyman, Patrick Brisbin@@ -45,7 +45,6 @@                    , persistent-template     >= 1.2       && < 1.4                    , http-conduit            >= 1.5                    , aeson                   >= 0.5-                   , pwstore-fast            >= 2.2                    , lifted-base             >= 0.1                    , blaze-html              >= 0.5                    , blaze-markup            >= 0.5.1@@ -57,6 +56,9 @@                    , resourcet                    , safe                    , time+                   , base64-bytestring+                   , byteable+                   , binary      exposed-modules: Yesod.Auth                      Yesod.Auth.BrowserId@@ -67,6 +69,7 @@                      Yesod.Auth.Message                      Yesod.Auth.GoogleEmail     other-modules:   Yesod.Auth.Routes+                     Yesod.PasswordStore     ghc-options:     -Wall  source-repository head