{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

{-|
Module      : Data.Password.Instances
Copyright   : (c) Dennis Gosnell, 2019
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'.

See the "Data.Password" module for more information.
-}

module Data.Password.Instances
  (
    -- * Plaintext Password
    Pass(..)
    -- * 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

import Data.Aeson (FromJSON)
import Data.Password
import Database.Persist.Class (PersistField)
import Database.Persist.Sql (PersistFieldSql)
import Web.HttpApiData (FromHttpApiData)


-- $setup
-- >>> :set -XOverloadedStrings
--
-- Import needed functions.
--
-- >>> import Data.Aeson (decode)
-- >>> import Data.Text (Text)
-- >>> import Database.Persist.Class (PersistField(toPersistValue))
-- >>> import Web.HttpApiData (parseUrlPiece)


-- | This instance allows a 'Pass' to be created from a JSON blob.
--
-- >>> let maybePass = decode "\"foobar\"" :: Maybe Pass
-- >>> fmap unsafeShowPassword maybePass
-- 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.
--
-- Similarly, there is no 'ToJSON' and 'FromJSON' instance for 'PassHash'
-- because we don't want to accidentally send the password hash to the end
-- user.
deriving newtype instance FromJSON Pass

-- | This instance allows a 'Pass' to be created with functions like
-- 'Web.HttpApiData.parseUrlPiece' or 'Web.HttpApiData.parseQueryParam'.
--
-- >>> let eitherPass = parseUrlPiece "foobar" :: Either Text Pass
-- >>> fmap unsafeShowPassword eitherPass
-- Right "foobar"
deriving newtype instance FromHttpApiData Pass

-- | This instance allows a 'PassHash' to be stored as a field in a database using
-- "Database.Persist".
--
-- >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"
-- >>> let pass = Pass "foobar"
-- >>> let hashedPassword = hashPassWithSalt (Pass "foobar") salt
-- >>> 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
-- want to make it easy to store a plain-text password in the database.
deriving newtype instance PersistField PassHash

-- | This instance allows a 'PassHash' to be stored as a field in an SQL
-- database in "Database.Persist.Sql".
deriving newtype instance PersistFieldSql PassHash