password-instances 1.0.0.0 → 2.0.0.0
raw patch · 9 files changed
+213/−81 lines, 9 filesdep +base-compatdep +tastydep +tasty-hunitdep ~aesondep ~basedep ~passwordbuild-type:Customsetup-changednew-uploaderPVP ok
version bump matches the API change (PVP)
Dependencies added: base-compat, tasty, tasty-hunit, tasty-quickcheck, template-haskell, text
Dependency ranges changed: aeson, base, password, persistent
API changes (from Hackage documentation)
- Data.Password.Instances: PassCheckFail :: PassCheck
- Data.Password.Instances: PassCheckSuccess :: PassCheck
- Data.Password.Instances: PassHash :: Text -> PassHash
- Data.Password.Instances: Salt :: ByteString -> Salt
- Data.Password.Instances: [getSalt] :: Salt -> ByteString
- Data.Password.Instances: [unPassHash] :: PassHash -> Text
- Data.Password.Instances: checkPass :: Pass -> PassHash -> PassCheck
- Data.Password.Instances: data Pass
- Data.Password.Instances: data PassCheck
- Data.Password.Instances: hashPass :: MonadIO m => Pass -> m PassHash
- Data.Password.Instances: hashPassWithSalt :: Salt -> Pass -> PassHash
- Data.Password.Instances: instance Data.Aeson.Types.FromJSON.FromJSON Data.Password.Pass
- Data.Password.Instances: instance Database.Persist.Class.PersistField.PersistField Data.Password.PassHash
- Data.Password.Instances: instance Database.Persist.Sql.Class.PersistFieldSql Data.Password.PassHash
- Data.Password.Instances: instance Web.Internal.HttpApiData.FromHttpApiData Data.Password.Pass
- Data.Password.Instances: mkPass :: Text -> Pass
- Data.Password.Instances: newSalt :: MonadIO m => m Salt
- Data.Password.Instances: newtype PassHash
- Data.Password.Instances: newtype Salt
- Data.Password.Instances: unsafeShowPassword :: Pass -> String
- Data.Password.Instances: unsafeShowPasswordText :: Pass -> Text
+ Data.Password.Instances: instance (TypeError ...) => Data.Aeson.Types.ToJSON.ToJSON Data.Password.Internal.Password
+ Data.Password.Instances: instance (TypeError ...) => Database.Persist.Class.PersistField.PersistField Data.Password.Internal.Password
+ Data.Password.Instances: instance (TypeError ...) => Web.Internal.HttpApiData.ToHttpApiData Data.Password.Internal.Password
+ Data.Password.Instances: instance Data.Aeson.Types.FromJSON.FromJSON Data.Password.Internal.Password
+ Data.Password.Instances: instance Database.Persist.Class.PersistField.PersistField (Data.Password.Internal.PasswordHash a)
+ Data.Password.Instances: instance Database.Persist.Sql.Class.PersistFieldSql (Data.Password.Internal.PasswordHash a)
+ Data.Password.Instances: instance Web.Internal.HttpApiData.FromHttpApiData Data.Password.Internal.Password
Files
- ChangeLog.md +10/−0
- LICENSE +4/−4
- README +11/−0
- README.md +0/−10
- Setup.hs +31/−0
- password-instances.cabal +32/−13
- src/Data/Password/Instances.hs +60/−49
- test/doctest/doctest.hs +11/−4
- test/tasty/Spec.hs +54/−1
ChangeLog.md view
@@ -1,5 +1,15 @@ # Changelog for password-instances +## 2.0.0.0++- No longer re-exports anything from `password` to be+ more predictable and in line with other `...-instances`+ packages like `quickcheck-instances` and `vector-instances`.+ [#8](https://github.com/cdepillabout/password/pull/8)+- Added instances for "forbidden" type classes with custom type errors.+ [#8](https://github.com/cdepillabout/password/pull/8)+- GHC versions < 8.2 are no longer actively supported. (Tested to work for GHC 8.2.2)+ ## 1.0.0.0 - Various changes re-exported from the `password` package.
LICENSE view
@@ -1,4 +1,4 @@-Copyright Dennis Gosnell (c) 2019+Copyright (c) Dennis Gosnell, 2019; Felix Paulusma, 2020 All rights reserved. @@ -13,9 +13,9 @@ disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Dennis Gosnell nor the names of other- contributors may be used to endorse or promote products derived- from this software without specific prior written permission.+ * Neither the name of Dennis Gosnell, Felix Paulusma nor the names+ of other contributors may be used to endorse or promote products+ derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ README view
@@ -0,0 +1,11 @@+# password-instances++[](http://travis-ci.org/cdepillabout/password)+[](https://hackage.haskell.org/package/password-instances)+[](http://stackage.org/lts/package/password-instances)+[](http://stackage.org/nightly/package/password-instances)+[](./LICENSE)++This package provides various typeclass instances for the plain-text password+and hashed password datatypes from the+[password](https://hackage.haskell.org/package/password) package.
− README.md
@@ -1,10 +0,0 @@-# password-instances--[](http://travis-ci.org/cdepillabout/password)-[](https://hackage.haskell.org/package/password-instances)-[](http://stackage.org/lts/package/password-instances)-[](http://stackage.org/nightly/package/password-instances)-[](./LICENSE)--This package provides various typeclass instances for the plain-text password-and hashed password datatypes from the [password](../password) package.
Setup.hs view
@@ -1,2 +1,33 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -Wall #-}+module Main (main) where++#ifndef MIN_VERSION_cabal_doctest+#define MIN_VERSION_cabal_doctest(x,y,z) 0+#endif++#if MIN_VERSION_cabal_doctest(1,0,0)++import Distribution.Extra.Doctest ( defaultMainWithDoctests )+main :: IO ()+main = defaultMainWithDoctests "doctests"++#else++#ifdef MIN_VERSION_Cabal+-- If the macro is defined, we have new cabal-install,+-- but for some reason we don't have cabal-doctest in package-db+--+-- Probably we are running cabal sdist, when otherwise using new-build+-- workflow+#warning You are configuring this package without cabal-doctest installed. \+ The doctests test-suite will not work as a result. \+ To fix this, install cabal-doctest before configuring.+#endif+ import Distribution.Simple++main :: IO () main = defaultMain++#endif
password-instances.cabal view
@@ -1,26 +1,32 @@ cabal-version: 1.12 name: password-instances-version: 1.0.0.0+version: 2.0.0.0 category: Data synopsis: typeclass instances for password package description: A library providing typeclass instances for common libraries for the types from the password package. homepage: https://github.com/cdepillabout/password/password-instances#readme bug-reports: https://github.com/cdepillabout/password/issues-author: Dennis Gosnell-maintainer: cdep.illabout@gmail.com-copyright: Copyright (c) 2019 Dennis Gosnell+author: Dennis Gosnell, Felix Paulusma+maintainer: cdep.illabout@gmail.com, felix.paulusma@gmail.com+copyright: Copyright (c) Dennis Gosnell, 2019; Felix Paulusma, 2020 license: BSD3 license-file: LICENSE-build-type: Simple+build-type: Custom extra-source-files:- README.md+ README ChangeLog.md source-repository head type: git location: https://github.com/cdepillabout/password +custom-setup+ setup-depends:+ base+ , Cabal+ , cabal-doctest >=1.0.6 && <1.1+ library hs-source-dirs: src@@ -29,17 +35,18 @@ other-modules: Paths_password_instances build-depends:- base >=4.7 && <5- , aeson+ base >= 4.9 && < 5+ , aeson >= 0.2 , http-api-data- , password- , persistent+ , password >= 2+ , persistent >= 1.2+ , text ghc-options: -Wall default-language: Haskell2010 -test-suite password-instances-doctest+test-suite doctests type: exitcode-stdio-1.0 hs-source-dirs:@@ -49,10 +56,13 @@ ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends:- base >=4.7 && <5+ base >=4.9 && <5+ , base-compat , doctest+ , password-instances , QuickCheck , quickcheck-instances+ , template-haskell default-language: Haskell2010 @@ -66,7 +76,16 @@ ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends:- base >=4.7 && <5+ base >=4.9 && <5 , password-instances+ , password+ , aeson+ , http-api-data+ , persistent+ , quickcheck-instances+ , tasty+ , tasty-hunit+ , tasty-quickcheck+ , text default-language: Haskell2010
src/Data/Password/Instances.hs view
@@ -1,103 +1,114 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-| Module : Data.Password.Instances-Copyright : (c) Dennis Gosnell, 2019+Copyright : (c) Dennis Gosnell, 2019; Felix Paulusma, 2020 License : BSD-style (see LICENSE file) Maintainer : cdep.illabout@gmail.com Stability : experimental Portability : POSIX -This module provides the same interface as "Data.Password", but it also-provides additional typeclass instances for 'Pass' and 'PassHash'.+This module provides additional typeclass instances+for 'Password' and 'PasswordHash'. See the "Data.Password" module for more information. -} -module Data.Password.Instances- (- -- * Plaintext Password- Pass- , mkPass- -- * Hashed Password- , PassHash(..)- , Salt(..)- -- * Functions for Hashing Plaintext Passwords- , hashPass- , hashPassWithSalt- , newSalt- -- * Functions for Checking Plaintext Passwords Against Hashed Passwords- , checkPass- , PassCheck(..)- -- * Unsafe Debugging Functions for Showing a Password- , unsafeShowPassword- , unsafeShowPasswordText- , -- * Setup for doctests.- -- $setup- ) where+module Data.Password.Instances () where -import Data.Aeson (FromJSON(..))-import Data.Password-import Database.Persist.Class (PersistField)-import Database.Persist.Sql (PersistFieldSql)-import Web.HttpApiData (FromHttpApiData(..))+import Data.Aeson (FromJSON(..), ToJSON(..))+import Data.Password (Password, PasswordHash(..), mkPassword)+import Data.Text.Encoding as TE (decodeUtf8)+import Database.Persist (PersistValue(..))+import Database.Persist.Class (PersistField(..))+import Database.Persist.Sql (PersistFieldSql(..))+import GHC.TypeLits (TypeError, ErrorMessage(..))+import Web.HttpApiData (FromHttpApiData(..), ToHttpApiData(..)) -- $setup -- >>> :set -XOverloadedStrings+-- >>> :set -XDataKinds -- -- Import needed functions. -- -- >>> import Data.Aeson (decode)+-- >>> import Data.Password (Salt(..), unsafeShowPassword)+-- >>> import Data.Password.Scrypt (defaultParams, hashPasswordWithSalt) -- >>> import Database.Persist.Class (PersistField(toPersistValue)) -- >>> import Web.HttpApiData (parseUrlPiece) --- | This instance allows a 'Pass' to be created from a JSON blob.+-- | This instance allows a 'Password' to be created from a JSON blob. ----- >>> let maybePass = decode "\"foobar\"" :: Maybe Pass--- >>> fmap unsafeShowPassword maybePass+-- >>> let maybePassword = decode "\"foobar\"" :: Maybe Password+-- >>> fmap unsafeShowPassword maybePassword -- Just "foobar" ----- There is no instance for 'ToJSON' for 'Pass' because we don't want to--- accidentally encode a plain-text 'Pass' to JSON and send it to the end-user.+-- There is no instance for 'ToJSON' for 'Password' because we don't want to+-- accidentally encode a plain-text 'Password' to JSON and send it to the end-user. ----- Similarly, there is no 'ToJSON' and 'FromJSON' instance for 'PassHash'+-- Similarly, there is no 'ToJSON' and 'FromJSON' instance for 'PasswordHash' -- because we don't want to accidentally send the password hash to the end -- user.-instance FromJSON Pass where- parseJSON = fmap mkPass . parseJSON+instance FromJSON Password where+ parseJSON = fmap mkPassword . parseJSON --- | This instance allows a 'Pass' to be created with functions like+type ErrMsg e = 'Text "Warning! Tried to convert plain-text Password to " ':<>: 'Text e ':<>: 'Text "!"+ ':$$: 'Text " This is likely a security leak. Please make sure whether this was intended."+ ':$$: 'Text " If this is intended, please use 'unsafeShowPassword' before converting to " ':<>: 'Text e+ ':$$: 'Text ""++-- | Type error! Do not use 'toJSON' on a 'Password'!+instance TypeError (ErrMsg "JSON") => ToJSON Password where+ toJSON = error "unreachable"++-- | This instance allows a 'Password' to be created with functions like -- 'Web.HttpApiData.parseUrlPiece' or 'Web.HttpApiData.parseQueryParam'. ----- >>> let eitherPass = parseUrlPiece "foobar"--- >>> fmap unsafeShowPassword eitherPass+-- >>> let eitherPassword = parseUrlPiece "foobar"+-- >>> fmap unsafeShowPassword eitherPassword -- Right "foobar"-instance FromHttpApiData Pass where- parseUrlPiece = fmap mkPass . parseUrlPiece+instance FromHttpApiData Password where+ parseUrlPiece = fmap mkPassword . parseUrlPiece --- | This instance allows a 'PassHash' to be stored as a field in a database using+-- | Type error! Do not transmit plain-text 'Password's over HTTP!+instance TypeError (ErrMsg "HttpApiData") => ToHttpApiData Password where+ toUrlPiece = error "unreachable"++-- | This instance allows a 'PasswordHash' to be stored as a field in a database using -- "Database.Persist". -- -- >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"--- >>> let pass = mkPass "foobar"--- >>> let hashedPassword = hashPassWithSalt salt pass+-- >>> let pass = mkPassword "foobar"+-- >>> let hashedPassword = hashPasswordWithSalt defaultParams salt pass -- >>> toPersistValue hashedPassword -- PersistText "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw==" -- -- In the example above, the long 'PersistText' will be the value you store in -- the database. ----- We don't provide an instance of 'PersistField' for 'Pass', because we don't+-- We don't provide an instance of 'PersistField' for 'Password', because we don't -- want to make it easy to store a plain-text password in the database.-deriving newtype instance PersistField PassHash+instance PersistField (PasswordHash a) where+ toPersistValue (PasswordHash hpw) = PersistText hpw+ fromPersistValue (PersistText txt) = Right $ PasswordHash txt+ fromPersistValue (PersistByteString bs) = Right $ PasswordHash $ TE.decodeUtf8 bs+ fromPersistValue _ = Left "did not parse PasswordHash from PersistValue" --- | This instance allows a 'PassHash' to be stored as a field in an SQL+-- | This instance allows a 'PasswordHash' to be stored as a field in an SQL -- database in "Database.Persist.Sql".-deriving newtype instance PersistFieldSql PassHash+deriving newtype instance PersistFieldSql (PasswordHash a)++-- | Type error! Do not store plain-text 'Password's in your database!+instance TypeError (ErrMsg "PersistValue") => PersistField Password where+ toPersistValue = error "unreachable"+ fromPersistValue = error "unreachable"
test/doctest/doctest.hs view
@@ -1,7 +1,14 @@--module Main (main) where+module Main where -import Test.DocTest+import Build_doctests (flags, pkgs, module_sources)+-- import Data.Foldable (traverse_)+import System.Environment.Compat (unsetEnv)+import Test.DocTest (doctest) main :: IO ()-main = doctest ["src"]+main = do+ -- traverse_ putStrLn args+ unsetEnv "GHC_ENVIRONMENT"+ doctest args+ where+ args = flags ++ pkgs ++ module_sources
test/tasty/Spec.hs view
@@ -1,2 +1,55 @@+{-# LANGUAGE OverloadedStrings #-}++import Data.Aeson+import Data.Aeson.Types (parseMaybe)+import Data.Text (Text)+import Database.Persist.Class (PersistField(..))+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck+import Test.QuickCheck.Instances.Text ()+import Web.HttpApiData (FromHttpApiData(..))++import Data.Password (Password, PasswordHash(..), unsafeShowPassword)+import Data.Password.Instances()++ main :: IO ()-main = putStrLn "Test suite not yet implemented"+main = defaultMain $ testGroup "Password Instances"+ [ aesonTest+ , fromHttpApiDataTest+ , persistTest+ ]++data TestUser = TestUser {+ name :: Text,+ password :: Password+} deriving (Show)++instance FromJSON TestUser where+ parseJSON = withObject "TestUser" $ \o ->+ TestUser <$> o .: "name" <*> o .: "password"+++aesonTest :: TestTree+aesonTest = testCase "Password (Aeson)" $+ assertEqual "password doesn't match" (Just testPassword) $+ unsafeShowPassword . password <$> parseMaybe parseJSON testUser+ where+ testPassword = "testpass"+ testUser = object+ [ "name" .= String "testname"+ , "password" .= String testPassword+ ]++fromHttpApiDataTest :: TestTree+fromHttpApiDataTest = testCase "Password (FromHttpApiData)" $+ assertEqual "password doesn't match" (Right testPassword) $+ unsafeShowPassword <$> parseUrlPiece testPassword+ where+ testPassword = "passtest"++persistTest :: TestTree+persistTest = testProperty "PasswordHash (PersistField)" $ \pass ->+ let pwd = PasswordHash pass+ in fromPersistValue (toPersistValue pwd) === Right pwd