password- Hashing and checking of passwords
Copyright(c) Hiroto Shioi 2020; Felix Paulusma 2020
LicenseBSD-style (see LICENSE file)
Safe HaskellNone



Password Validation

It is common for passwords to have a set of requirements. For example, a password might have to contain at least a certain amount of characters that consist of uppercase and lowercase alphabetic characters combined with numbers and/or other special characters.

This module provides an API which enables you to set up your own PasswordPolicy to validate the format of Passwords.

Password Policies

The most important part is to have a valid and robust PasswordPolicy.

A defaultPasswordPolicy_ is provided to quickly set up a "good-enough" validation of passwords, but you can also adjust it, or just create your own.

Just remember that a PasswordPolicy must be validated first to make sure it is actually a ValidPasswordPolicy. Otherwise, you'd never be able to validate any given Passwords.

Example usage

So let's say we're fine with the default policy, which requires the password to be between 8-64 characters, and have at least one lowercase, one uppercase and one digit character, then our function would look like the following:

myValidateFunc :: Password -> Bool
myValidateFunc = isValidPassword defaultPasswordPolicy_

Custom policies

But, for example, if you'd like to enforce that a Password includes at least one special character, and be at least 12 characters long, you'll have to make your own PasswordPolicy.

customPolicy :: PasswordPolicy
customPolicy =
    { minimumLength = 12
    , specialChars = 1

This custom policy will then have to be validated first, so it can be used to validate Passwords further on.

Template Haskell

The easiest way to validate a custom PasswordPolicy is by using a Template Haskell splice. Just turn on the {-# LANGUAGE TemplateHaskell #-} pragma, pass your policy to validatePasswordPolicyTH, surround it by $(...) and if it compiles it will be a ValidPasswordPolicy.

customValidPolicy :: ValidPasswordPolicy
customValidPolicy = $(validatePasswordPolicyTH customPolicy)

NB: any custom CharSetPredicate will be ignored by validatePasswordPolicyTH and replaced with the defaultCharSetPredicate. So if you want to use your own CharSetPredicate, you won't be able to validate your policy using validatePasswordPolicyTH. Most users, however, will find defaultCharSetPredicate to be sufficient.

At runtime

Another way of validating your custom policy is validatePasswordPolicy. In an application, this might be implemented in the following way.

main :: IO ()
main =
    case (validatePasswordPolicy customPolicy) of
      Left reasons -> error $ show reasons
      Right validPolicy -> app `runReaderT` validPolicy

customValidateFunc :: Password -> ReaderT ValidPasswordPolicy IO Bool
customValidateFunc pwd = do
    policy <- ask
    return $ isValidPassword policy pwd

Let's get dangerous

Or, if you like living on the edge, you could also just match on Right. I hope you're certain your policy is valid, though. So please have at least a unit test to verify that passing your PasswordPolicy to validatePasswordPolicy actually returns a Right.

Right validPolicy = validatePasswordPolicy customPolicy

customValidateFunc :: Password -> Bool
customValidateFunc = isValidPassword validPolicy

Validating passwords

The main function of this module is probably isValidPassword, as it is simple and straightforward.

Though if you'd want to know why a Password failed to validate, because you'd maybe like to communicate those InvalidReasons back to the user, validatePassword is here to help you out.

validatePassword :: ValidPasswordPolicy -> Password -> ValidationResult Source #

Checks if a given Password adheres to the provided ValidPasswordPolicy.

In case of an invalid password, returns the reasons why it wasn't valid.

>>> let pass = mkPassword "This_Is_Valid_Password1234"
>>> validatePassword defaultPasswordPolicy_ pass


isValidPassword :: ValidPasswordPolicy -> Password -> Bool Source #

This function is equivalent to:

validatePassword policy password == ValidPassword
>>> let pass = mkPassword "This_Is_Valid_PassWord1234"
>>> isValidPassword defaultPasswordPolicy_ pass


data ValidationResult Source #

Result of validating a Password.


Password Policy

A PasswordPolicy has to be validated before it can be used to validate a Password. This is done using validatePasswordPolicy or validatePasswordPolicyTH.

Next to the obvious lower and upper bounds for the length of a Password, a PasswordPolicy can dictate how many lowercase letters, uppercase letters, digits and/or special characters are minimally required to be used in the Password to be considered a valid Password.

An observant user might have also seen that a PasswordPolicy includes a CharSetPredicate. Very few users will want to change this from the defaultCharSetPredicate, since this includes all non-control ASCII characters.

If, for some reason, you'd like to accept more characters (e.g. é, ø, か, 事) or maybe you want to only allow alpha-numeric characters, charSetPredicate is the place to do so.

validatePasswordPolicy :: PasswordPolicy -> Either [InvalidPolicyReason] ValidPasswordPolicy Source #

Verifies that a PasswordPolicy is valid and converts it into a ValidPasswordPolicy.

>>> validatePasswordPolicy defaultPasswordPolicy
Right (...)


validatePasswordPolicyTH :: PasswordPolicy -> Q Exp Source #

Template Haskell validation function for PasswordPolicys.

{-# LANGUAGE TemplateHaskell #-}
myPolicy :: PasswordPolicy
myPolicy = defaultPasswordPolicy{ specialChars = 1 }

myValidPolicy :: ValidPasswordPolicy
myValidPolicy = $(validatePasswordPolicyTH myPolicy)

For technical reasons, the charSetPredicate field is ignored and the defaultCharSetPredicate is used. If, for any reason, you do need to use a custom CharSetPredicate, please use validatePasswordPolicy and either handle the failure case at runtime and/or use a unit test to make sure your policy is valid.


data PasswordPolicy Source #

Set of policies used to validate a Password.

When defining your own PasswordPolicy, please keep in mind that:

  • The value of maximumLength must be bigger than 0
  • The value of maximumLength must be bigger than minimumLength
  • If any other field has a negative value (e.g. lowercaseChars), it will be defaulted to 0
  • The total sum of all character category values (i.e. all fields ending in -Chars) must not be larger than the value of maximumLength.
  • The provided CharSetPredicate needs to allow at least one of the characters in the categories which require more than 0 characters. (e.g. if lowercaseChars is > 0, the charSetPredicate must allow at least one of the characters in ['a'..'z'])

or else the validation functions will return one or more InvalidPolicyReasons.

If you're unsure of what to do, please use the default: defaultPasswordPolicy_





fromValidPasswordPolicy :: ValidPasswordPolicy -> PasswordPolicy Source #

In case you'd want to retrieve the PasswordPolicy from the ValidPasswordPolicy


defaultPasswordPolicy :: PasswordPolicy Source #

Default value for the PasswordPolicy.

Enforces that a password must be between 8-64 characters long and have at least one uppercase letter, one lowercase letter and one digit, though can easily be adjusted by using record update syntax:

myPolicy = defaultPasswordPolicy{ specialChars = 1 }

This policy on it's own is guaranteed to be valid. Any changes made to it might result in validatePasswordPolicy returning one or more InvalidPolicyReasons.

>>> defaultPasswordPolicy
PasswordPolicy {minimumLength = 8, maximumLength = 64, uppercaseChars = 1, lowercaseChars = 1, specialChars = 0, digitChars = 1, charSetPredicate = <FUNCTION>}


defaultPasswordPolicy_ :: ValidPasswordPolicy Source #

Unchangeable defaultPasswordPolicy, but guaranteed to be valid.


newtype CharSetPredicate Source #

Predicate which defines the characters that can be used for a password.




defaultCharSetPredicate :: CharSetPredicate Source #

The default character set consists of uppercase and lowercase letters, numbers, and special characters from the ASCII character set. (i.e. everything from the ASCII set except the control characters)


data InvalidReason Source #

Possible reasons for a Password to be invalid.



PasswordTooShort !MinimumLength !ProvidedLength

Length of Password is too short.

PasswordTooLong !MaximumLength !ProvidedLength

Length of Password is too long.

NotEnoughReqChars !CharacterCategory !MinimumAmount !ProvidedAmount

Password does not contain required number of characters.

InvalidCharacters !Text

Password contains characters that cannot be used

data InvalidPolicyReason Source #

Possible reasons for a PasswordPolicy to be invalid



InvalidLength !MinimumLength !MaximumLength

Value of minimumLength is bigger than maximumLength

InvalidLength minimumLength maximumLength
MaxLengthBelowZero !MaximumLength

Value of maximumLength is zero or less

MaxLengthBelowZero maximumLength
CategoryAmountsAboveMaxLength !MaximumLength !Int

The total of the character category amount requirements are higher than the maximum length of the password. (i.e. the Int signifies the total of lowercaseChars + uppercaseChars + digitChars + specialChars)

CategoryAmountsAboveMaxLength maximumLength totalRequiredChars
InvalidCharSetPredicate !CharacterCategory !MinimumAmount

charSetPredicate does not return True for a CharacterCategory that requires at least MinimumAmount characters in the password

For internal use

These are used in the test suite. You should not need these.

These are basically internal functions and as such have NO guarantee (NONE) to be consistent between releases.

defaultCharSet :: String Source #

Default character set

Should be all non-control characters in the ASCII character set.

validateCharSetPredicate :: PasswordPolicy -> [InvalidPolicyReason] Source #

Validate CharSetPredicate to return True on at least one of the characters that is required.

For instance, if PasswordPolicy states that the password requires at least one uppercase letter, then CharSetPredicate should return True on at least one uppercase letter.

categoryToPredicate :: CharacterCategory -> Char -> Bool Source #

Convert a CharacterCategory into its associated predicate function

isSpecial :: Char -> Bool Source #

Check if given Char is a special character. (i.e. any non-alphanumeric non-control ASCII character)

allButCSP :: PasswordPolicy -> [Int] Source #

All Int fields of the PasswordPolicy in a row