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

Copyright(c) Patrick Brisbin 2010, Paul Rouse 2014-2016
LicenseMIT
MaintainerPaul Rouse <pyr@doynton.org>
StabilityStable
PortabilityPortable
Safe HaskellNone
LanguageHaskell98

Yesod.Auth.HashDB

Contents

Description

A Yesod authentication plugin designed to look users up in a Persistent database where the hash of their password is stored.

Release 1.5 is the first of two which remove compatibility with old (pre 1.3) databases. Some other deprecated functionality is removed too. Please see https://github.com/paul-rouse/yesod-auth-hashdb/blob/master/Upgrading.md

To use this in a Yesod application, the foundation data type must be an instance of YesodPersist, and the username and hashed passwords should be added to the database. The following 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 to uniquely identify users
    password Text Maybe   -- password hash for HashDB
    UniqueUser name

Create an instance of HashDBUser for this data type:

import Yesod.Auth.HashDB (HashDBUser(..))
....
instance HashDBUser User where
    userPasswordHash = userPassword
    setPasswordHash h u = u { userPassword = Just h }

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

import Yesod.Auth.HashDB (authHashDB)
....
instance YesodAuth App where
    ....
    authPlugins _ = [ authHashDB (Just . UniqueUser), .... ]
    getAuthId creds = ... -- Perhaps modify scaffolding: see below

The argument to authHashDB is a function which takes a Text and produces a Maybe containing a Unique value to look up in the User table. The example (Just . UniqueUser) shown here works for the model outlined above.

In the scaffolding, the definition of getAuthId contains code to add a user who is not already in the database. Depending on how users are administered, this may not make sense when using HashDB, so consider whether it should be removed.

For a real application, the developer should provide some sort of of administrative interface for setting passwords; it needs to call setPassword and save the result in the database. However, if you need to initialise the database by hand, you can generate the correct password hash as follows:

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

where "17" is the default strength parameter (defaultStrength) 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 where Source

The type representing user information stored in the database should be an instance of this class. It just provides the getters and setters used by the functions in this module.

Minimal complete definition

userPasswordHash, setPasswordHash

Methods

userPasswordHash :: user -> Maybe Text Source

Retrieve password hash from user data

userPasswordSalt :: user -> Maybe Text Source

Deprecated: Verification against old data containing a separate salt field will be removed in version 1.6

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.

setPasswordHash Source

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.

defaultStrength :: Int Source

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

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

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 user Source

validatePass :: HashDBUser u => u -> Text -> Maybe Bool Source

Validate a plaintext password against the hash in the user data structure. This function retains compatibility with user data produced by old versions of this module (prior to 1.3), although the hashes 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.

The result distinguishes two types of validation failure, which may be useful in an application which supports multiple authentication methods:

  • Just False - the user has a password set up, but the given one does not match it
  • Nothing - the user does not have a password (the hash is Nothing)

Since 1.4.1

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

Upgrade existing user credentials to a stronger hash. The existing hash will have been produced from a weaker setting in the current algorithm. Use this function to produce an updated user record to store in the database.

As of version 1.5 this function cannot be used to upgrade a hash which has a non-empty separate salt field. Such entries would have been produced originally by versions of this module prior to 1.3, but may have been upgraded using earlier versions of this function.

Returns Nothing if the user has no password (ie if userPasswordHash u is Nothing), if the password hash is not in the new format, or if the user has a non-empty salt field resulting from the old-style hashing algorithm.

Interface to database and Yesod.Auth

validateUser Source

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 simply looks up the user id in the database and calls validatePass to do the work.

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

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 site Source

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