packages feed

password-aeson-0.1.0.0: src/Data/Password/Aeson.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

{-|
Module      : Data.Password.Aeson
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 additional typeclass instances
for 'Password' and 'PasswordHash', along with the
'ExposedPassword' newtype if you /absolutely have to/
convert a plain text password into JSON.

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

module Data.Password.Aeson (ExposedPassword (..)) where

import Data.Aeson (FromJSON(..), ToJSON(..))
import Data.Password.Types (
  mkPassword,
  unsafeShowPassword,
  Password,
  PasswordHash (PasswordHash),
 )
import GHC.TypeLits (TypeError, ErrorMessage(..))

-- $setup
-- >>> :set -XOverloadedStrings
-- >>> :set -XDataKinds
--
-- Import needed functions.
--
-- >>> import Data.Aeson (decode)
-- >>> import Data.Password.Bcrypt (Salt(..), hashPasswordWithSalt, unsafeShowPassword)

-- | 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 = 'Text "Warning! Tried to convert plain-text Password to JSON!"
         :$$: '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 JSON"
         :$$: 'Text ""

-- | Type error! Do not use 'toJSON' on a 'Password'!
instance TypeError ErrMsg => ToJSON Password where
  toJSON = error "unreachable"

-- | WARNING: DO NOT USE UNLESS ABSOLUTELY NECESSARY!
--
-- Using this newtype will allow your plain text password to be turned into
-- JSON. Keep this type tightly bound to only the section where you want to
-- expose the `Password`, since it's easy for a bigger type that contains
-- this `ExposedPassword` to be logged or printed as JSON, and now you've
-- accidentally leaked passwords in your logs or database.
newtype ExposedPassword = ExposedPassword Password
  deriving newtype (FromJSON)

instance ToJSON ExposedPassword where
  toJSON (ExposedPassword p) = toJSON $ unsafeShowPassword p

deriving newtype instance FromJSON (PasswordHash a)

deriving newtype instance ToJSON (PasswordHash a)