packages feed

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 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++[![Build Status](https://secure.travis-ci.org/cdepillabout/password.svg)](http://travis-ci.org/cdepillabout/password)+[![Hackage](https://img.shields.io/hackage/v/password-instances.svg)](https://hackage.haskell.org/package/password-instances)+[![Stackage LTS](http://stackage.org/package/password-instances/badge/lts)](http://stackage.org/lts/package/password-instances)+[![Stackage Nightly](http://stackage.org/package/password-instances/badge/nightly)](http://stackage.org/nightly/package/password-instances)+[![BSD3 license](https://img.shields.io/badge/license-BSD3-blue.svg)](./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--[![Build Status](https://secure.travis-ci.org/cdepillabout/password.svg)](http://travis-ci.org/cdepillabout/password)-[![Hackage](https://img.shields.io/hackage/v/password-instances.svg)](https://hackage.haskell.org/package/password-instances)-[![Stackage LTS](http://stackage.org/package/password-instances/badge/lts)](http://stackage.org/lts/package/password-instances)-[![Stackage Nightly](http://stackage.org/package/password-instances/badge/nightly)](http://stackage.org/nightly/package/password-instances)-[![BSD3 license](https://img.shields.io/badge/license-BSD3-blue.svg)](./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