2

I'd like to write a function that checks if user's login and hashed password exists in database. Let's assume DB is very simple:

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
    User
        login String
        passHash Hash
        UniqueL login
        deriving Eq Show

Where Hash is an alias type Hash = PasswordHash PBKDF2 from https://hackage.haskell.org/package/password-2.0.1.1/docs/Data-Password-PBKDF2.html.

Also, for simplicity let's assume that both login and password are passed to my function as String.

There's a function in the module called checkPassword (https://hackage.haskell.org/package/password-2.0.1.1/docs/Data-Password-PBKDF2.html#g:3) that checks if a password produces the required hash. However, I don't know how can I use it in esqualeto where_ clause.

I came up with something like this:

type DB m a = ReaderT SqlBackend m a

checkCredentials ::(MonadIO m, MonadLogger m) => (String, String) -> DB m Bool
checkCredentials (login, password) = do
    let hashFunc = (checkPassword $ mkPassword (pack password))
    res <- 
        select $ 
            from $ \user -> do 
                -- This won't wwork
                where_ (user ^. UserLogin ==. val login &&. hashFunc (user ^. UserPassHash) ==. val PasswordCheckSuccess)
                return user
    return (negate $ null res)

but the part hashFunc (user ^. UserPassHash) obviously won't work. I guess I need to enter with my hashFunc into a monad (or two), but I have no idea how to do this.

I noticed that UserPassHash is of type EntityField User Hash. I wonder how can I turn it into EntityField User PasswordCheck.

LA.27
  • 1,888
  • 19
  • 35
  • 5
    The problem is that `where_` will convert to an SQL expression, so you can not use arbitrary functions here, since the SQL backend would not understand this. Often the credentials are checked at the application level, so you fetch the user with the username, and then check if the hashed password matches the given one. – Willem Van Onsem Sep 21 '20 at 23:58
  • I see. This makes sense. So the best way is to try getting a user by login (as in my snippet - into the "res" value) and then checking the password hash on application side (i.e. outside of the SQL query), right ? If so - @WillemVanOnsem can you post an answer so that I can accept it ? – LA.27 Sep 22 '20 at 07:45
  • Uhm, the code above looks weird. It looks like you are trying to hash the hashed password, which is wrong. If your password hashes are not "salted", then you could write the correct query `... &&. user ^. UserPassHash ==. hash password` (for some proper `hash` function) which would work because the hash is computed by Haskell, and its result is then injected as a constant in the formed SQL query (no need for SQL to know the `hash` function ). If the passwords are salted (as they should, for security) then you need to follow Willem's advice and extract the salt+hash, then check it in Haskell. – chi Sep 22 '20 at 12:08
  • In any case, even if there is no "salt", Willem's advice will work. – chi Sep 22 '20 at 12:09
  • Long story short: The library takes care of salt itself. For details you can check the doc link I provided for checkPassword function. – LA.27 Sep 22 '20 at 12:51

0 Answers0