content: {-|
  Module: IHP.AuthSupport.Authentication
  Description: Authentication functions
  Copyright: (c) digitally induced GmbH, 2020
  -}
  module IHP.AuthSupport.Authentication (verifyPassword, hashPassword, generateAuthenticationToken, Lockable (maxSignInAttemps), VerifiyPassword (..)) where
  
  import IHP.Prelude
  import qualified Crypto.PasswordStore
  import qualified Test.RandomStrings
  
  class Lockable entity where
      maxSignInAttemps :: entity -> Int
  
  passwordStrength :: Int
  passwordStrength = 17
  
  -- | Creates a password hash
  -- 
  -- __Example:__
  -- 
  -- > action CreateUserAction = do
  -- >     newRecord @User
  -- >         |> fill @'["passwordHash"]
  -- >         |> validateField nonEmpty #passwordHash
  -- >         |> ifValid \case
  -- >             Left user -> ..
  -- >             Right user -> do
  -- >                 user <- user.passwordHash |> liftIO . hashPassword
  -- >                 user <- createRecord user
  -- > 
  hashPassword :: Text -> IO Text
  hashPassword plainText = cs <$> Crypto.PasswordStore.makePassword (cs plainText) passwordStrength
  {-# INLINE hashPassword #-}
  
  
  class VerifiyPassword a where
      verifyPassword' :: a -> Text -> Bool
  
  instance VerifiyPassword Text where
      verifyPassword' passwordHash plainText = Crypto.PasswordStore.verifyPassword (cs plainText) (cs passwordHash)
  
  instance VerifiyPassword (Maybe Text) where
      verifyPassword' (Just passwordHash) plainText = verifyPassword' passwordHash plainText
      verifyPassword' Nothing _ = False
  
  -- | Returns @True@ when a given non-hashed password matches the hashed password of the given user.
  --
  -- >>> user <- query @User |> filterWhere (#email, "hunter2@outlook.com") |> fetchOne
  -- >>> verifyPassword user "hunter2"
  -- True
  verifyPassword :: (HasField "passwordHash" entity passwordField, VerifiyPassword passwordField) => entity -> Text -> Bool
  verifyPassword entity plainText = verifyPassword' entity.passwordHash plainText
  {-# INLINE verifyPassword #-}
  
  
  -- | Generates a 32 character random string
  --
  -- >>> token <- generateAuthenticationToken
  -- "11D3OAbUfL0P9KNJ09VcUfCO0S9RwI"
  generateAuthenticationToken :: IO Text
  generateAuthenticationToken = cs <$> Test.RandomStrings.randomWord Test.RandomStrings.randomASCII 32
  {-# INLINE generateAuthenticationToken #-}