packages feed

sel-0.1.0.0: test/Test/Hashing/Password.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.Hashing.Password where

import Data.Function (on)
import Data.Maybe (isNothing)
import Data.Text (Text)
import qualified Data.Text as Text
import Test.Tasty
import Test.Tasty.HUnit

import qualified Sel.Hashing.Password as Sel

spec :: TestTree
spec =
  testGroup
    "Password hashing tests"
    [ testCase "Round-trip test for password hashing" testRoundtripHash
    , testCase "Consistent password hashing with salt" testHashPasswordWSalt
    , testCase "ASCII representation" testASCIIRepresentation
    ]

testRoundtripHash :: Assertion
testRoundtripHash = do
  let password = "hunter2" :: Text
  passwordHash <- Sel.hashText password
  let passwordHash' = Sel.asciiByteStringToPasswordHash $ Sel.passwordHashToByteString passwordHash

  assertEqual
    "Original hash and hash from bytestring are the same"
    passwordHash
    passwordHash'

  assertBool
    "Password hashing is consistent"
    (Sel.verifyText passwordHash "hunter2")

  assertBool
    "Password hashing is consistent"
    (Sel.verifyText passwordHash' "hunter2")

testHashPasswordWSalt :: Assertion
testHashPasswordWSalt = do
  let hashWSalt s = Sel.hashByteStringWithParams Sel.defaultArgon2Params s
      password = "hunter2"
      cmpPWHashes = on (==) Sel.passwordHashToByteString

  salt1 <- Sel.genSalt
  let hashOrig = hashWSalt salt1 password
      hashOrig' = hashWSalt salt1 password
  assertBool
    "Password hashing with salt is consistent"
    (cmpPWHashes hashOrig hashOrig')

  hashWoSalt <- Sel.hashByteString password
  assertBool
    "Password hashing with salt differs from without"
    (not $ cmpPWHashes hashOrig hashWoSalt)

  salt2 <- Sel.genSalt
  let hashWNewSalt = hashWSalt salt2 password
  assertBool
    "Password hashing differs with a new salt"
    (not $ cmpPWHashes hashOrig hashWNewSalt)

  assertBool
    "Bogus salt ByteString fails to generate Salt"
    (isNothing (Sel.hexByteStringToSalt "deadbeef"))

testASCIIRepresentation :: Assertion
testASCIIRepresentation = do
  hash <- Sel.hashByteString "hunter3"
  let textHash = Sel.passwordHashToText hash
  assertBool
    "Textual representation is stable using passwordHashToText"
    ("$argon2id$v=19$m=262144,t=3,p=1$" `Text.isPrefixOf` textHash)

  let bsHash = Sel.passwordHashToByteString hash
  let hash2 = Sel.asciiByteStringToPasswordHash bsHash
  assertEqual
    "Can import hash"
    hash2
    hash