yesod-auth-hashdb-1.4.0: Authentication plugin for Yesod.

PortabilityPortable
StabilityStable
MaintainerPaul Rouse <pyr@doynton.org>
Safe HaskellNone

Yesod.Auth.HashDB

Contents

Description

A yesod-auth AuthPlugin designed to look users up in Persist where their user id's and a hash of their password is stored.

This module was removed from yesod-auth-1.3.0.0 and is now maintained separately. Versions of this module prior to yesod-auth-1.3 used a relatively weak hashing algorithm (a single round of SHA1) which does not provide adequate protection against an attacker who discovers the hashed passwords. See: https://github.com/yesodweb/yesod/issues/668.

It has now been rewritten to use Crypto.PasswordStore, but this has been done in a way which preserves compatibility both with the API and with databases which have been set up using older versions of this module. There are two levels of database compatibility:

  • The verification code recognises both the old and new hash formats, so passwords can be verified against database entries which still contain old-style hashes.
  • The function upgradePasswordHash can be used to migrate existing user records to use the new format hash. Unlike freshly created password hashes, entries converted this way must still have the old salt field, since the old hash function remains part of the algorithm needed for verification. (The new hash is layered on top of the old one.)

On the other hand, new passwords set up by setPassword or setPasswordStrength no longer use a separate salt field, so new users of this module need only provide a single password field in the user data, and can ignore the salt.

In a system which has been migrated from the old format, passwords which are reset using the new format will have an empty salt field. Once all the entries are of this form, it is safe to change the model to remove the salt, and change the HashDBUser instance accordingly.

To use this in a Yesod application, it must be an instance of YesodPersist, and the username and hashed-passwords should be added to the database. The followng steps give an outline of what is required.

You need a database table to store user records: in a scaffolded site it might look like:

 User
     name Text             -- user name used by HashDB
     password Text Maybe   -- password hash for HashDB
     UniqueUser name

Create an instance of HashDBUser for this data type:

 instance HashDBUser User where
     userPasswordHash = userPassword
     setPasswordHash h p = p { userPassword = Just h }

In the YesodAuth instance declaration for your app, include authHashDB like so:

 instance YesodAuth App where
     ....
     authPlugins _ = [ authHashDB (Just . UniqueUser), .... ]
     getAuthId = getAuthIdHashDB AuthR (Just . UniqueUser)

AuthR should be your authentication route, and the function (Just . UniqueUser) supplied to both authHashDB and getAuthIdHashDB takes a Text and produces a Unique value to look up in the User table. getAuthIdHashDB is just a convenience for the case when HashDB is the only plugin, and something else would be needed when other plugins are used as well.

You can create password hashes manually as follows, if you need to initialise the database:

 ghci -XOverloadedStrings
 > import Crypto.PasswordStore
 > makePassword "MyPassword" 14

where "14" is the default strength parameter used in this module.

== Custom Login Form

Instead of using the built-in HTML form, a custom one can be supplied by using authHashDBWithForm instead of authHashDB.

The custom form needs to be given as a function returning a Widget, since it has to build in the supplied action URL, and it must provide two text fields called username and password. For example, the following modification of the outline code given above would replace the default form with a very minimal one which has no labels and a simple layout.

 instance YesodAuth App where
     ....
     authPlugins _ = [ authHashDBWithForm myform (Just . UniqueUser), .... ]

 myform :: Route App -> Widget
 myform action = $(whamletFile "templates/loginform.hamlet")

where templates/loginform.hamlet contains

 <form method="post" action="@{action}">
     <input name="username">
     <input type="password" name="password">
     <input type="submit" value="Login">

Synopsis

Documentation

class HashDBUser user whereSource

Interface for data type which holds user info. It's just a collection of getters and setters

Methods

userPasswordHash :: user -> Maybe TextSource

Retrieve password hash from user data

userPasswordSalt :: user -> Maybe TextSource

Retrieve salt for password from user data. This is needed only for compatibility with old database entries, which contain the salt as a separate field. New implementations do not require a separate salt field in the user data, and should leave this as the default.

setPasswordHashSource

Arguments

:: Text

Password hash

-> user 
-> user 

Callback for setPassword and upgradePasswordHash. Produces a version of the user data with the hash set to the new value.

This is the method which you should define for new applications, which do not require compatibility with databases containing hashes written by previous versions of this module. If you do need compatibility, define setSaltAndPasswordHash instead.

setUserHashAndSaltSource

Arguments

:: Text

Salt

-> Text

Password hash

-> user 
-> user 

Deprecated: Please use setSaltAndPasswordHash instead

setSaltAndPasswordHashSource

Arguments

:: Text

Salt

-> Text

Password hash

-> user 
-> user 

Callback used in upgradePasswordHash when compatibility is needed with old-style hashes (including ones already upgraded using upgradePasswordHash). This is not required for new applications, which do not have a separate salt field in user data: please define setPasswordHash instead.

The default implementation produces a runtime error, and will only be called if a non-empty salt value needs to be set for compatibility with an old database.

Instances

data family Unique record1

Unique keys besided the Key

defaultStrength :: IntSource

Default strength used for passwords (see Crypto.PasswordStore for details).

setPasswordStrength :: (MonadIO m, HashDBUser user) => Int -> Text -> user -> m userSource

Set password for user, using the given strength setting. Use this function, or setPassword, to produce a user record containing the hashed password. Unlike previous versions of this module, no separate salt field is required for new passwords (but it may still be required for compatibility while old password hashes remain in the database).

This function does not change the database; the calling application is responsible for saving the data which is returned.

setPassword :: (MonadIO m, HashDBUser user) => Text -> user -> m userSource

upgradePasswordHash :: (MonadIO m, HashDBUser user) => Int -> user -> m (Maybe user)Source

Upgrade existing user credentials to a stronger hash. The existing hash may have been produced either by previous versions of this module, which used a weak algorithm, or from a weaker setting in the current algorithm. Use this function to produce an updated user record to store in the database.

To allow transitional use, starting from hashes produced by older versions of this module, and upgrading them to the new format, we have to use the hash alone, without knowledge of the user's plaintext password. In this case, we apply the new algorithm to the old hash, resulting in both hash functions, old and new, being used one on top of the other; this situation is recognised by the hash having the new format while the separate salt field is non-empty.

Returns Nothing if the user has no password (ie if userPasswordHash u is Nothing and/or userPasswordSalt u is Nothing).

Authentication

validateUserSource

Arguments

:: HashDBPersist site user 
=> Unique user

User unique identifier

-> Text

Password in plaintext

-> HandlerT site IO Bool 

Given a user ID and password in plaintext, validate them against the database values. This function retains compatibility with databases containing hashes produced by previous versions of this module, although they are less secure and should be upgraded as soon as possible. They can be upgraded using upgradePasswordHash, or by insisting that users set new passwords.

authHashDB :: HashDBPersist site user => (Text -> Maybe (Unique user)) -> AuthPlugin siteSource

Prompt for username and password, validate that against a database which holds the username and a hash of the password

authHashDBWithForm :: HashDBPersist site user => (Route site -> WidgetT site IO ()) -> (Text -> Maybe (Unique user)) -> AuthPlugin siteSource

Like authHashDB, but with an extra parameter to supply a custom HTML form.

The custom form should be specified as a function which takes a route to use as the form action, and returns a Widget containing the form. The form must use the supplied route as its action URL, and, when submitted, it must send two text fields called username and password.

Please see the example in the documentation at the head of this module.

Since 1.3.2

getAuthIdHashDBSource

Arguments

:: HashDBPersist site user 
=> (AuthRoute -> Route site)

your site's Auth Route

-> (Text -> Maybe (Unique user))

gets user ID

-> Creds site

the creds argument

-> HandlerT site IO (Maybe (AuthId site)) 

A drop in for the getAuthId method of your YesodAuth instance which can be used if authHashDB is the only plugin in use.

Predefined data type

type User = UserGeneric SqlBackendSource

Deprecated: The predefined User data type will be removed soon - please define your own database table and accompanying instance of HashDBUser

data UserGeneric backend Source

Generate data base instances for a valid user

Constructors

User

Deprecated: The predefined User data type will be removed soon - please define your own database table and accompanying instance of HashDBUser

Instances

Typeable1 UserGeneric 
PersistStore backend => ToBackendKey backend (UserGeneric backend) 
Eq (BackendKey backend) => Eq (Key (UserGeneric backend)) 
Ord (BackendKey backend) => Ord (Key (UserGeneric backend)) 
Read (BackendKey backend) => Read (Key (UserGeneric backend)) 
Show (BackendKey backend) => Show (Key (UserGeneric backend)) 
ToJSON (BackendKey backend) => ToJSON (Key (UserGeneric backend)) 
FromJSON (BackendKey backend) => FromJSON (Key (UserGeneric backend)) 
PathPiece (BackendKey backend) => PathPiece (Key (UserGeneric backend)) 
PersistFieldSql (BackendKey backend) => PersistFieldSql (Key (UserGeneric backend)) 
PersistStore backend => PersistFieldSql (UserGeneric backend) 
PersistStore backend => PersistEntity (UserGeneric backend) 
PersistField (BackendKey backend) => PersistField (Key (UserGeneric backend)) 
PersistStore backend => PersistField (UserGeneric backend) 
HashDBUser (UserGeneric backend) 

data family EntityField record1 ($a)

An EntityField is parameterised by the Haskell record it belongs to and the additional type of that field

migrateUsers :: MigrationSource

Deprecated: The predefined User data type will be removed soon - please define your own database table and accompanying instance of HashDBUser