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 +6/−3
- Yesod/Auth/Message.hs +8/−9
- Yesod/PasswordStore.hs +429/−0
- yesod-auth.cabal +5/−2
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}+ + <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