packages feed

yesod-auth-hashdb 1.5.1.3 → 1.6

raw patch · 11 files changed

+303/−106 lines, 11 filesdep +aesondep +unordered-containersdep −cryptohash

Dependencies added: aeson, unordered-containers

Dependencies removed: cryptohash

Files

ChangeLog.md view
@@ -1,3 +1,20 @@+## 1.6++This release completes the breaking changes started in 1.5.  For details+of upgrading, please see+[Upgrading.md](https://github.com/paul-rouse/yesod-auth-hashdb/blob/master/Upgrading.md).++* Complete removal of compatibility with old databases designed for versions before 1.3+* Add JSON support++## 1.5.1.3++* Fix test failure with basic-prelude >= 0.6 (#6)++## 1.5.1.2++* Relax upper bound to allow persistent-2.6+ ## 1.5.1.1  * Minor documentation improvement
Yesod/Auth/HashDB.hs view
@@ -17,9 +17,8 @@ -- A Yesod authentication plugin designed to look users up in a Persistent -- database where the hash of their password is stored. ----- __Release 1.5 is the first of two which remove compatibility with old__--- __(pre 1.3) databases.  Some other deprecated functionality is removed__--- __too.  Please see__+-- __Releases 1.6 finishes the process of removing compatibility with old__+-- __(pre 1.3) databases.  Please see__ -- __<https://github.com/paul-rouse/yesod-auth-hashdb/blob/master/Upgrading.md>__ -- -- To use this in a Yesod application, the foundation data type must be an@@ -105,6 +104,52 @@ -- included in the widget to add it - see @defaultForm@ in the source -- code of this module for an example. --+-- == JSON Interface+--+-- This plugin provides sufficient tools to build a complete JSON-based+-- authentication flow.  We assume that a design goal is to avoid URLs+-- being built into the client, so all of the URLs needed are passed in+-- JSON data.+--+-- To start the process, Yesod's defaultErrorHandler produces a JSON+-- response if the HTTP Accept header gives \"application/json\"+-- precedence over HTML.  For a NotAuthenticated error, the status is+-- 401 and the response contains the URL to use for authentication: this+-- is the route which will be handled by the loginHandler method of the+-- YesodAuth instance, which normally returns a login form.+--+-- Leaving the loginHandler aside for a moment, the final step - supported+-- by this plugin since version 1.6 - is to POST the credentials for+-- authentication in a JSON object.  This object must include the+-- properties "username" and "password".  In the HTML case this would be+-- the form submission, but here we want to use JSON instead.+--+-- In a JSON interface, the purpose of the loginHandler is to tell the+-- client the URL for submitting the credentials.  This requires a+-- custom loginHandler, since the default one generates HTML only.+-- It can find the correct URL by using the 'submitRouteHashDB'+-- function defined in this module.+--+-- Writing the loginHandler is made a little messy by the fact that its+-- type allows only HTML content.  A work-around is to send JSON as a+-- short-circuit response, but we still make the choice using selectRep+-- so as to get its matching of content types.  Here is an example which+-- is geared around using HashDB on its own, supporting both JSON and HTML+-- clients:+--+-- > instance YesodAuth App where+-- >    ....+-- >    loginHandler = do+-- >         submission <- submitRouteHashDB+-- >         render <- lift getUrlRender+-- >         typedContent@(TypedContent ct _) <- selectRep $ do+-- >             provideRepType typeHtml $ return emptyContent+                                -- Dummy: the real Html version is at the end+-- >             provideJson $ object [("loginUrl", toJSON $ render submission)]+-- >         when (ct == typeJson) $+-- >             sendResponse typedContent   -- Short-circuit JSON response+-- >         defaultLoginHandler             -- Html response+-- ------------------------------------------------------------------------------- module Yesod.Auth.HashDB     ( HashDBUser(..)@@ -117,18 +162,19 @@     , validateUser     , authHashDB     , authHashDBWithForm+    , submitRouteHashDB     ) where   #if __GLASGOW_HASKELL__ < 710-import           Control.Applicative         ((<$>), (<*>))+import           Control.Applicative         ((<$>), (<*>), pure) #endif-import qualified Crypto.Hash           as CH (SHA1, Digest, hash) import           Crypto.PasswordStore        (makePassword, strengthenPassword,                                               verifyPassword, passwordStrength)+import           Data.Aeson                  ((.:?)) import qualified Data.ByteString.Char8 as BS (pack, unpack) import           Data.Maybe                  (fromMaybe)-import           Data.Text                   (Text, pack, unpack, append)+import           Data.Text                   (Text, pack, unpack) import           Yesod.Auth import qualified Yesod.Auth.Message    as Msg import           Yesod.Core@@ -146,36 +192,23 @@ defaultStrength = 17  -- | The type representing user information stored in the database should---   be an instance of this class.  It just provides the getters and setters+--   be an instance of this class.  It just provides the getter and setter --   used by the functions in this module. class HashDBUser user where-    -- | Retrieve password hash from user data+    -- | Getter used by 'validatePass' and 'upgradePasswordHash' to+    --   retrieve the password hash from user data+    --     userPasswordHash :: user -> Maybe Text-    -- | Retrieve salt for password from user data.  This is needed only for-    --   compatibility with old database entries, which contain the salt-    --   as a separate field.  New implementations do not require a separate-    --   salt field in the user data, and should leave this as the default.-    userPasswordSalt :: user -> Maybe Text-    userPasswordSalt _ = Just "" -    -- | Callback for 'setPassword' and 'upgradePasswordHash'.  Produces a+    -- | Setter used by 'setPassword' and 'upgradePasswordHash'.  Produces a     --   version of the user data with the hash set to the new value.     --     setPasswordHash :: Text   -- ^ Password hash                        -> user -> user      {-# MINIMAL userPasswordHash, setPasswordHash #-}-{-# DEPRECATED userPasswordSalt "Verification against old data containing a separate salt field will be removed in version 1.6" #-}  --- | Calculate salted hash using SHA1.  Retained for compatibility with---   hashes in existing databases, but will not be used for new passwords.-saltedHash :: Text              -- ^ Salt-           -> Text              -- ^ Password-           -> Text-saltedHash salt pw =-    pack $ show (CH.hash $ BS.pack $ unpack $ append salt pw :: CH.Digest CH.SHA1)- -- | Calculate a new-style password hash using "Crypto.PasswordStore". passwordHash :: MonadIO m => Int -> Text -> m Text passwordHash strength pwd = do@@ -200,11 +233,6 @@ setPassword = setPasswordStrength defaultStrength  -- | Validate a plaintext password against the hash in the user data structure.---   This function retains compatibility with user data produced by old---   versions of this module (prior to 1.3), although the hashes are less---   secure and should be upgraded as soon as possible.  They can be---   upgraded using 'upgradePasswordHash', or by insisting that users set---   new passwords. -- --   The result distinguishes two types of validation failure, which may --   be useful in an application which supports multiple authentication@@ -213,27 +241,24 @@ --   * Just False - the user has a password set up, but the given one does --     not match it -----   * Nothing - the user does not have a password (the hash is Nothing)+--   * Nothing - the user does not have a password ('userPasswordHash' returns+--     Nothing) -- --   Since 1.4.1 -- validatePass :: HashDBUser u => u -> Text -> Maybe Bool validatePass user passwd = do     hash <- userPasswordHash user-    salt <- userPasswordSalt user-    -- NB plaintext password characters are truncated to 8 bits here, and also-    -- in saltedHash and passwordHash above (the hash and old salt are already-    -- 8 bit).  This is for historical compatibility, but in practice it is+    -- NB plaintext password characters are truncated to 8 bits here,+    -- and also in passwordHash above (the hash is already 8 bit).+    -- This is for historical compatibility, but in practice it is     -- unlikely to reduce the entropy of most users' alphabets by much.     let hash' = BS.pack $ unpack hash-        passwd' = BS.pack $ unpack $ if salt == "" then passwd-                                     else-                                         -- Extra layer for an upgraded old hash-                                         saltedHash salt passwd+        passwd' = BS.pack $ unpack passwd     if passwordStrength hash' > 0-        -- Will give >0 for new-style hash, else fall back+        -- Will give >0 for valid hash format, else treat as if wrong password         then return $ verifyPassword passwd' hash'-        else return $ hash == saltedHash salt passwd+        else return False  -- | Upgrade existing user credentials to a stronger hash.  The existing --   hash will have been produced from a weaker setting in the current@@ -246,25 +271,21 @@ --   but may have been upgraded using earlier versions of this function. -- --   Returns Nothing if the user has no password (ie if 'userPasswordHash' u---   is 'Nothing'), if the password hash is not in the new format, or if the---   user has a non-empty salt field resulting from the old-style hashing---   algorithm.+--   is 'Nothing') or if the password hash is not in the correct format.+-- upgradePasswordHash :: (MonadIO m, HashDBUser user) => Int -> user -> m (Maybe user) upgradePasswordHash strength u = do     let old = userPasswordHash u-        oldSalt = fromMaybe "" $ userPasswordSalt u     case old of         Just oldHash -> do             let oldHash' = BS.pack $ unpack oldHash             if passwordStrength oldHash' > 0               then-                -- Already a new-style hash, so only strengthen it as needed+                -- Valid hash format, so strengthen it as needed                 let newHash = pack $ BS.unpack $ strengthenPassword oldHash' strength-                in if oldSalt == ""-                   then return $ Just $ setPasswordHash newHash u-                   else return Nothing   -- Can't handle non-empty salt+                in return $ Just $ setPasswordHash newHash u               else do-                -- Old-style hash: cannot upgrade+                -- Invalid hash format (perhaps from old version of this module)                 return Nothing         Nothing -> return Nothing @@ -288,6 +309,15 @@     , PersistEntity user     ) +-- Internal data type for receiving JSON encoded username and password+data UserPass = UserPass (Maybe Text) (Maybe Text)++instance FromJSON UserPass where+    parseJSON (Object v) = UserPass <$>+                           v .:? "username" <*>+                           v .:? "password"+    parseJSON _          = pure $ UserPass Nothing Nothing+ -- | Given a user ID and password in plaintext, validate them against --   the database values.  This function simply looks up the user id in the --   database and calls 'validatePass' to do the work.@@ -308,13 +338,21 @@  -- | Handle the login form. First parameter is function which maps --   username (whatever it might be) to unique user ID.+--+--   Since version 1.6, the data may be submitted as a JSON object.+--   See the \"JSON Interface\" section above for more details. postLoginR :: HashDBPersist site user =>               (Text -> Maybe (Unique user))            -> HandlerT Auth (HandlerT site IO) TypedContent postLoginR uniq = do-    (mu,mp) <- lift $ runInputPost $ (,)-        <$> iopt textField "username"-        <*> iopt textField "password"+    ct <- lookupHeader "Content-Type"+    let jsonContent = ((== "application/json") . simpleContentType) <$> ct+    UserPass mu mp <-+        case jsonContent of+          Just True -> requireJsonBody+          _         -> lift $ runInputPost $ UserPass+                       <$> iopt textField "username"+                       <*> iopt textField "password"      isValid <- lift $ fromMaybe (return False)                   (validateUser <$> (uniq =<< mu) <*> mp)@@ -386,3 +424,17 @@             }      |]+++-- | The route, in the parent site, to which the username and password+--   should be sent in order to log in.  This function is particularly+--   useful in constructing a 'loginHandler' function which provides a+--   JSON response.  See the \"JSON Interface\" section above for more+--   details.+--+--   Since 1.6+submitRouteHashDB :: YesodAuth site =>+                  HandlerT Auth (HandlerT site IO) (Route site)+submitRouteHashDB = do+    toParent <- getRouteToParent+    return $ toParent login
stack.yaml view
@@ -1,4 +1,4 @@-resolver: lts-5.4+resolver: lts-6.6 packages:  - . extra-deps: []
test/ExampleData.hs view
@@ -12,13 +12,16 @@     oldStyleUpgradedUser,     newStyleValidUser,     newStyleBadUser,-    newStyleInOldFormat,     stronger ) where  import Yesod.Auth.HashDB (HashDBUser(..), defaultStrength) import Data.Text (Text) +-- OldStyleUser is retained in the tests so that naive (incorrect)+-- upgrading can be tested; the naive upgrade consists of removing+-- the `userPasswordSalt` method without setting new passwords which+-- have empty salt. data OldStyleUser = OldStyleUser {                         oldStyleName :: Text,                         oldStylePass :: Maybe Text,@@ -27,7 +30,6 @@  instance HashDBUser OldStyleUser where     userPasswordHash = oldStylePass-    userPasswordSalt = oldStyleSalt     setPasswordHash h u = u { oldStyleSalt = Just "",                               oldStylePass = Just h                             }@@ -85,12 +87,6 @@ newStyleBadUser =     NewStyleUser "bad"                  Nothing--newStyleInOldFormat :: OldStyleUser-newStyleInOldFormat =-    OldStyleUser "fox"-                 (Just "sha256|14|2hL7cNopkA/dGy/5CQTuSg==|CUTPW6ICMISSjohFep851f9PdqIn7Y4B75/I77BvEYM=")-                 (Just "")  stronger :: Int stronger = defaultStrength + 2
test/IntegrationTest.hs view
@@ -8,6 +8,9 @@ ) where  import BasicPrelude+import Data.Aeson                   (FromJSON, parseJSON, (.:))+import qualified Data.Aeson as JSON+import Network.Wai.Test             (simpleBody) import Test.Hspec                   (Spec, SpecWith, before,                                      describe, context, it) import qualified Yesod.Test as YT@@ -25,6 +28,37 @@ withApp app = before $ return app #endif +authUrl :: Text+authUrl = "http://localhost:3000/auth/login"++data AuthUrl = AuthUrl Text deriving Eq+instance FromJSON AuthUrl where+    parseJSON (JSON.Object v) = AuthUrl <$> v .: "authentication_url"+    parseJSON _ = mempty++loginUrl :: Text+loginUrl = "http://localhost:3000/auth/page/hashdb/login"++data LoginUrl = LoginUrl Text deriving Eq+instance FromJSON LoginUrl where+    parseJSON (JSON.Object v) = LoginUrl <$> v .: "loginUrl"+    parseJSON _ = mempty++successMsg :: Text+successMsg = "Login Successful"++data SuccessMsg = SuccessMsg Text deriving Eq+instance FromJSON SuccessMsg where+    parseJSON (JSON.Object v) = SuccessMsg <$> v .: "message"+    parseJSON _ = mempty++getBodyJSON :: FromJSON a => YT.YesodExample site (Maybe a)+getBodyJSON = do+    resp <- YT.getResponse+    let body = simpleBody <$> resp+        result = JSON.decode =<< body+    return result+ integrationSpec :: SpecWith MyTestApp integrationSpec = do     describe "The home page" $ do@@ -70,5 +104,45 @@         loc <- doLoginPart1 "paul" "WrongPassword"         checkFailedLogin loc       it "fails when unknown user name given" $ do-        loc <- doLoginPart1 "paul" "WrongPassword"+        loc <- doLoginPart1 "xyzzy" "WrongPassword"         checkFailedLogin loc++    describe "JSON Login" $ do+      it "JSON access to protected page gives JSON object with auth URL" $ do+        YT.request $ do+          YT.setMethod "GET"+          YT.setUrl ProtectedR+          YT.addRequestHeader ("Accept", "application/json")+        YT.statusIs 401+        auth <- getBodyJSON+        YT.assertEqual "Authentication URL" auth (Just $ AuthUrl authUrl)+      it "Custom loginHandler using submitRouteHashDB has correct URL in JSON" $ do+        YT.request $ do+          YT.setMethod "GET"+          YT.setUrl authUrl+          YT.addRequestHeader ("Accept", "application/json")+        YT.statusIs 200+        login <- getBodyJSON+        YT.assertEqual "Login URL" login (Just $ LoginUrl loginUrl)+#if MIN_VERSION_yesod_test(1,5,0)+      -- Disable this example for yesod-test < 1.5.0.1, since it uses the wrong+      -- content type for JSON (https://github.com/yesodweb/yesod/issues/1063).+      it "Sending JSON username and password produces JSON success message" $ do+        -- This first request is only to get the CSRF token cookie, used below+        YT.request $ do+          YT.setMethod "GET"+          YT.setUrl authUrl+          YT.addRequestHeader ("Accept", "application/json")+        YT.request $ do+          YT.setMethod "POST"+          YT.setUrl loginUrl+          YT.addRequestHeader ("Accept", "application/json")+          YT.addRequestHeader ("Content-Type", "application/json; charset=utf-8")+          YT.setRequestBody "{\"username\":\"paul\",\"password\":\"MyPassword\"}"+          -- CSRF token is being checked, since yesod-core >= 1.4.14 is a+          -- dependency of yesod-test >= 1.5 on which this item is conditional.+          YT.addTokenFromCookie+        YT.statusIs 200+        msg <- getBodyJSON+        YT.assertEqual "Login success" msg (Just $ SuccessMsg successMsg)+#endif
test/NonDBTests.hs view
@@ -17,23 +17,23 @@      describe "validatePass" $ do -      context "Old-style valid user" $ do-        it "verifies OK with correct password" $-            validatePass oldStyleValidUser mypassword `shouldBe` Just True-        it "fails validation with a wrong password giving Just False" $+      context "Naively upgraded old-style valid user" $ do+        it "no longer checks good password (wrong format, so Just False)" $+            validatePass oldStyleValidUser mypassword `shouldBe` Just False+        it "no longer checks bad password (wrong format, so Just False)" $             validatePass oldStyleValidUser changedpw `shouldBe` Just False -      context "Old-style user with no password hash" $ do+      context "Naively upgraded old-style user with no password hash" $ do         it "fails validation giving Nothing" $             validatePass oldStyleBadUser1 mypassword `shouldBe` Nothing -      context "Old-style user with no salt" $ do-        it "fails validation giving Nothing" $-            validatePass oldStyleBadUser2 mypassword `shouldBe` Nothing+      context "Naively upgraded old-style user with no salt" $ do+        it "fails validation (wrong format, so Just False)" $+            validatePass oldStyleBadUser2 mypassword `shouldBe` Just False        context "Previously upgraded old-style user" $ do-        it "verifies OK with correct password" $-            validatePass oldStyleUpgradedUser mypassword `shouldBe` Just True+        it "no longer verifies with correct password as it still needs salt" $+            validatePass oldStyleUpgradedUser mypassword `shouldBe` Just False         it "fails validation with a wrong password giving Just False" $             validatePass oldStyleUpgradedUser changedpw `shouldBe` Just False @@ -47,27 +47,23 @@         it "fails validation giving Nothing" $             validatePass newStyleBadUser mypassword `shouldBe` Nothing -      context "New-style hash in old-style format with blank salt" $ do-        it "verifies OK with correct password" $-            validatePass newStyleInOldFormat mypassword `shouldBe` Just True-        it "fails validation with a wrong password giving Just False" $-            validatePass newStyleInOldFormat changedpw `shouldBe` Just False -     describe "upgradePasswordHash" $ do -      context "Upgrade of old-style password hash" $ do-        it "is Nothing if the user has a valid password hash" $ do+      context "Upgrade of naively upgraded old-style password hash" $ do+        it "is Nothing if the user has a password (format is wrong)" $ do             newuser <- upgradePasswordHash defaultStrength oldStyleValidUser             newuser `shouldBe` Nothing-        it "is Nothing if there is no password hash (but salt non-empty)" $ do+        it "is Nothing if there is no password hash" $ do             newuser <- upgradePasswordHash defaultStrength oldStyleBadUser1             newuser `shouldBe` Nothing        context "Upgrade of previously upgraded old-style user" $ do-        it "is Nothing since salt is non-empty" $ do+        it "silently succeeds producing a password which won't verify as it still needs salt" $ do             newuser <- upgradePasswordHash defaultStrength oldStyleUpgradedUser-            newuser `shouldBe` Nothing+            case newuser of+              Nothing -> expectationFailure "should not produce Nothing"+              Just u  -> validatePass u mypassword `shouldBe` Just False        context "Upgrade of new-style password hash to stronger setting" $ do         it "really does have a hash containing the new strength" $ do@@ -83,33 +79,20 @@             newuser <- upgradePasswordHash stronger newStyleBadUser             newuser `shouldBe` Nothing -      context "Upgrade of new-style hash in old-style format with blank salt" $ do-        it "really does have a hash containing the new strength" $ do-            newuser <- upgradePasswordHash stronger newStyleInOldFormat-            let s = pack $ "|" ++ show stronger ++ "|"-                found = fmap (s `isInfixOf`) $ newuser >>= oldStylePass-            found `shouldBe` Just True-        it "still verifies with the same password" $ do-            newuser <- upgradePasswordHash stronger newStyleInOldFormat-            let valid = newuser >>= flip validatePass mypassword-            valid `shouldBe` Just True -     describe "setPassword" $ do         it "produces hash which verifies OK starting from old-style user" $ do             newuser <- setPassword changedpw oldStyleValidUser             validatePass newuser changedpw `shouldBe` Just True         it "produces empty salt (in the case of old-style user)" $ do             newuser <- setPassword changedpw oldStyleValidUser-            userPasswordSalt newuser `shouldBe` Just ""+            oldStyleSalt newuser `shouldBe` Just ""         it "produces hash which verifies OK starting from new-style user" $ do             newuser <- setPassword changedpw newStyleValidUser             validatePass newuser changedpw `shouldBe` Just True       describe "Only the bottom 8 bits of password characters are used" $ do-        it "when verifying old-style password hash" $-            validatePass oldStyleValidUser equivpassword `shouldBe` Just True         it "when verifying new-style password hash" $             validatePass newStyleValidUser equivpassword `shouldBe` Just True         it "when setting a new password" $ do
test/TestSite.hs view
@@ -23,16 +23,25 @@ import Control.Applicative          ((<$>)) import Data.Typeable                (Typeable) #endif+import Control.Monad                (when) import Data.Text import Database.Persist.Sqlite import Network.HTTP.Client.Conduit  (Manager) import Yesod import Yesod.Auth-import Yesod.Auth.HashDB            (HashDBUser(..), authHashDB)+import Yesod.Auth.HashDB            (HashDBUser(..), authHashDB,+                                     submitRouteHashDB) import Yesod.Auth.Message           (AuthMessage (InvalidLogin))-#if MIN_VERSION_yesod_core(1,4,14)-import Yesod.Core                   (defaultCsrfMiddleware,-                                     defaultYesodMiddleware)++#if ! MIN_VERSION_yesod_auth(1,4,9)+import Yesod.Auth.Message           (AuthMessage (LoginTitle))+defaultLoginHandler :: AuthHandler master Html+defaultLoginHandler = do+    tp <- getRouteToParent+    lift $ authLayout $ do+        setTitleI LoginTitle+        master <- getYesod+        mapM_ (flip apLogin tp) (authPlugins master) #endif  @@ -104,6 +113,17 @@     authPlugins _ = [ authHashDB (Just . UniqueUser) ]      authHttpManager = appHttpManager++    loginHandler = do+        submission <- submitRouteHashDB+        render <- lift getUrlRender+        typedContent@(TypedContent ct _) <- selectRep $ do+            provideRepType typeHtml $ return emptyContent+                           -- Dummy: the real Html version is at the end+            provideJson $ object [("loginUrl", toJSON $ render submission)]+        when (ct == typeJson) $+            sendResponse typedContent   -- Short-circuit JSON response+        defaultLoginHandler             -- Html response  instance YesodAuthPersist App 
test/integration.hs view
@@ -9,7 +9,9 @@ import Yesod import Yesod.Auth.HashDB            (setPassword) +#ifndef STANDALONE import IntegrationTest+#endif import TestSite  main :: IO ()@@ -19,7 +21,10 @@         validUser <- setPassword "MyPassword" $ User "paul" Nothing         insert_ validUser     mgr <- newManager-    -- To try this as a server, replace the `hspec` line below with this one,-    -- and remove the import of IntegrationTest above.-    -- warp 3000 $ App mgr conn+#ifdef STANDALONE+    -- Run as a stand-alone server - see the cabal file in this directory+    warp 3000 $ App mgr conn+#else+    -- Otherwise run the tests     hspec $ withApp (App mgr conn) integrationSpec+#endif
+ test/stack.yaml view
@@ -0,0 +1,7 @@+# Used for building standalone-testsite+packages:+- ..+- .+extra-deps: []+resolver: lts-6.6+compiler-check: newer-minor
+ test/standalone-testsite.cabal view
@@ -0,0 +1,39 @@+name:            standalone-testsite+version:         0.1.1+license:         MIT+author:          Paul Rouse+maintainer:      Paul Rouse <pyr@doynton.org>+synopsis:        Stand-alone version of test for Yesod.Auth.HashDB+category:        Web, Yesod+stability:       Stable+cabal-version:   >= 1.8.0+build-type:      Simple+homepage:        https://github.com/paul-rouse/yesod-auth-hashdb+bug-reports:     https://github.com/paul-rouse/yesod-auth-hashdb/issues+description:+    Stand-alone integration test for Yesod.Auth.HashDB, run as a server.+    .+    Normally the integration test is run using Yesod.Test.  However, it+    may be handy to build the example server as a stand-alone application+    and debug it.  To do so, use this cabal file and the accompanying+    stack.yaml.  TestSite.hs needs MIN_VERSION_yesod_test, so a dummy+    is provided here.  STANDALONE is used in integration.hs to replace+    the tests with code which uses warp to make a server.++executable integration+    main-is:         integration.hs+    hs-source-dirs:  .+    ghc-options:     -Wall+    cpp-options:     -DMIN_VERSION_yesod_test(a,b,c)=1 -DSTANDALONE+    other-modules:   TestSite+    build-depends:   base >= 4 && < 5+                   , hspec+                   , http-conduit+                   , monad-logger+                   , persistent-sqlite+                   , resourcet+                   , text+                   , yesod+                   , yesod-auth+                   , yesod-auth-hashdb+                   , yesod-core
yesod-auth-hashdb.cabal view
@@ -1,5 +1,5 @@ name:            yesod-auth-hashdb-version:         1.5.1.3+version:         1.6 license:         MIT license-file:    LICENSE author:          Patrick Brisbin, later changes Paul Rouse@@ -28,6 +28,8 @@     if you wish to use email verification to set up accounts). extra-source-files:  ChangeLog.md                      stack.yaml+                     test/stack.yaml+                     test/standalone-testsite.cabal  library     build-depends:   base                    >= 4          && < 5@@ -39,7 +41,7 @@                    , persistent              >= 2.1        && < 2.7                    , yesod-form              >= 1.4        && < 1.5                    , pwstore-fast            >= 2.2-                   , cryptohash              >= 0.8+                   , aeson      exposed-modules: Yesod.Auth.HashDB     ghc-options:     -Wall@@ -65,6 +67,7 @@                    , TestSite                    , TestTools     build-depends:   base >= 4 && < 5+                   , aeson                    , bytestring                    , basic-prelude                    , containers@@ -76,6 +79,7 @@                    , persistent-sqlite                    , resourcet                    , text+                   , unordered-containers                    , wai-extra                    , yesod                    , yesod-auth