password-instances 3.0.0.0 → 3.1.0.0
raw patch · 6 files changed
+58/−256 lines, 6 filesdep +password-aesondep +password-http-api-datadep +password-persistentdep −QuickCheckdep −aesondep −base-compatsetup-changedPVP ok
version bump matches the API change (PVP)
Dependencies added: password-aeson, password-http-api-data, password-persistent
Dependencies removed: QuickCheck, aeson, base-compat, doctest, http-api-data, password, password-instances, password-types, persistent, quickcheck-instances, tasty, tasty-hunit, tasty-quickcheck, template-haskell, text
API changes (from Hackage documentation)
- Data.Password.Instances: instance (TypeError ...) => Data.Aeson.Types.ToJSON.ToJSON Data.Password.Types.Password
- Data.Password.Instances: instance (TypeError ...) => Database.Persist.Class.PersistField.PersistField Data.Password.Types.Password
- Data.Password.Instances: instance (TypeError ...) => Web.Internal.HttpApiData.ToHttpApiData Data.Password.Types.Password
- Data.Password.Instances: instance Data.Aeson.Types.FromJSON.FromJSON Data.Password.Types.Password
- Data.Password.Instances: instance Database.Persist.Class.PersistField.PersistField (Data.Password.Types.PasswordHash a)
- Data.Password.Instances: instance Database.Persist.Sql.Class.PersistFieldSql (Data.Password.Types.PasswordHash a)
- Data.Password.Instances: instance Web.Internal.HttpApiData.FromHttpApiData Data.Password.Types.Password
Files
- ChangeLog.md +12/−1
- Setup.hs +0/−29
- password-instances.cabal +33/−55
- src/Data/Password/Instances.hs +13/−102
- test/doctest/doctest.hs +0/−14
- test/tasty/Spec.hs +0/−55
ChangeLog.md view
@@ -1,12 +1,23 @@ # Changelog for `password-instances` +## 3.1.0.0++- Added cabal flags (`aeson`, `http-api-data`, `persistent`) to make+ dependencies optional. All flags default to `True`.+ [#89](https://github.com/cdepillabout/password/pull/89)+- Added compile-time error if all flags are disabled.+- Split into dedicated packages.+ [#87](https://github.com/cdepillabout/password/pull/87)++Thanks to [@blackheaven](https://github.com/blackheaven)+ ## 3.0.0.0 - The `password-instances` library now depends on the new `password-types` package, instead of on the `password` package. [#40](https://github.com/cdepillabout/password/pull/40) Thanks to [@Vlix](https://github.com/Vlix)-- Small update to the README+- Small update to the README. - Changed `PersistField` instance of `PasswordHash` to use `decodeUtf8'` for better handling of UTF8 decoding exceptions. [#43](https://github.com/cdepillabout/password/pull/43)
Setup.hs view
@@ -1,33 +1,4 @@-{-# 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,8 +1,8 @@ cabal-version: 1.12 name: password-instances-version: 3.0.0.0-category: Data+version: 3.1.0.0+category: Security 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/tree/master/password-instances#readme@@ -21,11 +21,25 @@ type: git location: https://github.com/cdepillabout/password +flag aeson+ description: Enable instances for Aeson+ default: True+ manual: True++flag http-api-data+ description: Enable instances for HTTP API Data+ default: True+ manual: True++flag persistent+ description: Enable instances for Persistent+ default: True+ manual: True+ custom-setup setup-depends:- base- , Cabal- , cabal-doctest >=1.0.6 && <1.1+ base >= 4.9 && < 5+ , Cabal < 4 library hs-source-dirs:@@ -36,57 +50,21 @@ Paths_password_instances build-depends: base >= 4.9 && < 5- , aeson >= 0.2- , http-api-data- , password-types < 2- , persistent >= 1.2- , text ghc-options: -Wall default-language: Haskell2010--test-suite doctests- type:- exitcode-stdio-1.0- hs-source-dirs:- test/doctest- main-is:- doctest.hs- ghc-options:- -threaded -rtsopts -with-rtsopts=-N- build-depends:- base >=4.9 && <5- , base-compat- , doctest- , password- , password-instances- , QuickCheck- , quickcheck-instances- , template-haskell- default-language:- Haskell2010--test-suite password-instances-tasty- type:- exitcode-stdio-1.0- hs-source-dirs:- test/tasty- main-is:- Spec.hs- ghc-options:- -threaded -rtsopts -with-rtsopts=-N- build-depends:- base >=4.9 && <5- , password-instances- , password-types- , aeson- , http-api-data- , persistent- , quickcheck-instances- , tasty- , tasty-hunit- , tasty-quickcheck- , text- default-language:- Haskell2010+ default-extensions:+ CPP+ if flag(aeson)+ build-depends:+ password-aeson+ cpp-options: -DFLAG_AESON+ if flag(http-api-data)+ build-depends:+ password-http-api-data+ cpp-options: -DFLAG_HTTP_API_DATA+ if flag(persistent)+ build-depends:+ password-persistent+ cpp-options: -DFLAG_PERSISTENT
src/Data/Password/Instances.hs view
@@ -1,14 +1,4 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# OPTIONS_GHC -Wno-dodgy-exports -Wno-unused-imports #-} {-| Module : Data.Password.Instances@@ -24,99 +14,20 @@ See the "Data.Password.Types" module for more information. -} -module Data.Password.Instances () where+module Data.Password.Instances (module E) where -import Data.Aeson (FromJSON(..), ToJSON(..))-import Data.Password.Types (Password, PasswordHash(..), mkPassword)-#if !MIN_VERSION_base(4,13,0)-import Data.Semigroup ((<>))+#if !defined(FLAG_AESON) && !defined(FLAG_HTTP_API_DATA) && !defined(FLAG_PERSISTENT)+#error "At least one of the flags (aeson, http-api-data, persistent) must be enabled" #endif-import Data.Text (pack)-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.Bcrypt (Salt(..), hashPasswordWithSalt, unsafeShowPassword)--- >>> import Database.Persist.Class (PersistField(toPersistValue))--- >>> import Web.HttpApiData (parseUrlPiece)---- | This instance allows a 'Password' to be created from a JSON blob.------ >>> let maybePassword = decode "\"foobar\"" :: Maybe Password--- >>> fmap unsafeShowPassword maybePassword--- Just "foobar"------ 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 'PasswordHash'--- because we don't want to accidentally send the password hash to the end--- user.-instance FromJSON Password where- parseJSON = fmap mkPassword . parseJSON--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 eitherPassword = parseUrlPiece "foobar"--- >>> fmap unsafeShowPassword eitherPassword--- Right "foobar"-instance FromHttpApiData Password where- parseUrlPiece = fmap mkPassword . parseUrlPiece---- | 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 "abcdefghijklmnop"--- >>> let pass = mkPassword "foobar"--- >>> let hashedPassword = hashPasswordWithSalt 10 salt pass--- >>> toPersistValue hashedPassword--- PersistText "$2b$10$WUHhXETkX0fnYkrqZU3ta.N8Utt4U77kW4RVbchzgvBvBBEEdCD/u"------ 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 'Password', because we don't--- want to make it easy to store a plain-text password in the database.-instance PersistField (PasswordHash a) where- toPersistValue (PasswordHash hpw) = PersistText hpw- fromPersistValue = \case- PersistText txt -> Right $ PasswordHash txt- PersistByteString bs ->- either failed (Right . PasswordHash) $ TE.decodeUtf8' bs- _ -> Left "did not parse PasswordHash from PersistValue"- where- failed e = Left $ "Failed decoding PasswordHash to UTF8: " <> pack (show e)+#ifdef FLAG_AESON+import Data.Password.Aeson as E+#endif --- | This instance allows a 'PasswordHash' to be stored as a field in an SQL--- database in "Database.Persist.Sql".-deriving newtype instance PersistFieldSql (PasswordHash a)+#ifdef FLAG_HTTP_API_DATA+import Data.Password.HttpApiData as E+#endif --- | 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"+#ifdef FLAG_PERSISTENT+import Data.Password.Persistent as E+#endif
− test/doctest/doctest.hs
@@ -1,14 +0,0 @@-module Main where--import Build_doctests (flags, pkgs, module_sources)--- import Data.Foldable (traverse_)-import System.Environment.Compat (unsetEnv)-import Test.DocTest (doctest)--main :: IO ()-main = do- -- traverse_ putStrLn args- unsetEnv "GHC_ENVIRONMENT"- doctest args- where- args = flags ++ pkgs ++ module_sources
− test/tasty/Spec.hs
@@ -1,55 +0,0 @@-{-# 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.Types (Password, PasswordHash(..), unsafeShowPassword)-import Data.Password.Instances()---main :: IO ()-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