Portability | Portable |
---|---|
Stability | Stable |
Maintainer | Paul Rouse <pyr@doynton.org> |
Safe Haskell | None |
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.
- class HashDBUser user where
- userPasswordHash :: user -> Maybe Text
- userPasswordSalt :: user -> Maybe Text
- setPasswordHash :: Text -> user -> user
- setUserHashAndSalt :: Text -> Text -> user -> user
- setSaltAndPasswordHash :: Text -> Text -> user -> user
- data family Unique record1
- defaultStrength :: Int
- setPasswordStrength :: (MonadIO m, HashDBUser user) => Int -> Text -> user -> m user
- setPassword :: (MonadIO m, HashDBUser user) => Text -> user -> m user
- upgradePasswordHash :: (MonadIO m, HashDBUser user) => Int -> user -> m (Maybe user)
- validateUser :: (YesodPersist yesod, b ~ YesodPersistBackend yesod, PersistMonadBackend (b (HandlerT yesod IO)) ~ PersistEntityBackend user, PersistUnique (b (HandlerT yesod IO)), PersistEntity user, HashDBUser user) => Unique user -> Text -> HandlerT yesod IO Bool
- authHashDB :: (YesodAuth m, YesodPersist m, HashDBUser user, PersistEntity user, b ~ YesodPersistBackend m, PersistMonadBackend (b (HandlerT m IO)) ~ PersistEntityBackend user, PersistUnique (b (HandlerT m IO))) => (Text -> Maybe (Unique user)) -> AuthPlugin m
- getAuthIdHashDB :: (YesodAuth master, YesodPersist master, HashDBUser user, PersistEntity user, Key user ~ AuthId master, b ~ YesodPersistBackend master, PersistMonadBackend (b (HandlerT master IO)) ~ PersistEntityBackend user, PersistUnique (b (HandlerT master IO))) => (AuthRoute -> Route master) -> (Text -> Maybe (Unique user)) -> Creds master -> HandlerT master IO (Maybe (AuthId master))
- type User = UserGeneric SqlBackend
- data UserGeneric backend = User {
- userUsername :: !Text
- userPassword :: !Text
- userSalt :: !Text
- type UserId = KeyBackend SqlBackend User
- data family EntityField record1 ($a)
- migrateUsers :: forall m. (MonadBaseControl IO m, MonadIO m, MonadLogger m) => Migration (SqlPersistT m)
Documentation
class HashDBUser user whereSource
Interface for data type which holds user info. It's just a collection of getters and setters
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.
:: 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.
Deprecated: Please use setSaltAndPasswordHash instead
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.
HashDBUser (UserGeneric backend) |
data family Unique record1
Unique keys besided the Key
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).
setPassword :: (MonadIO m, HashDBUser user) => Text -> user -> m userSource
As setPasswordStrength
, but using the defaultStrength
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
).
Authentification
:: (YesodPersist yesod, b ~ YesodPersistBackend yesod, PersistMonadBackend (b (HandlerT yesod IO)) ~ PersistEntityBackend user, PersistUnique (b (HandlerT yesod IO)), PersistEntity user, HashDBUser user) | |
=> Unique user | User unique identifier |
-> Text | Password in plaint-text |
-> HandlerT yesod 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 :: (YesodAuth m, YesodPersist m, HashDBUser user, PersistEntity user, b ~ YesodPersistBackend m, PersistMonadBackend (b (HandlerT m IO)) ~ PersistEntityBackend user, PersistUnique (b (HandlerT m IO))) => (Text -> Maybe (Unique user)) -> AuthPlugin mSource
Prompt for username and password, validate that against a database which holds the username and a hash of the password
:: (YesodAuth master, YesodPersist master, HashDBUser user, PersistEntity user, Key user ~ AuthId master, b ~ YesodPersistBackend master, PersistMonadBackend (b (HandlerT master IO)) ~ PersistEntityBackend user, PersistUnique (b (HandlerT master IO))) | |
=> (AuthRoute -> Route master) | your site's Auth Route |
-> (Text -> Maybe (Unique user)) | gets user ID |
-> Creds master | the creds argument |
-> HandlerT master IO (Maybe (AuthId master)) |
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
data UserGeneric backend Source
Generate data base instances for a valid user
User | |
|
Typeable1 UserGeneric | |
PersistFieldSql (UserGeneric backend) | |
PersistEntity (UserGeneric backend) | |
PersistField (UserGeneric backend) | |
HashDBUser (UserGeneric backend) |
type UserId = KeyBackend SqlBackend UserSource
data family EntityField record1 ($a)
An EntityField
is parameterised by the Haskell record it belongs to
and the additional type of that field
migrateUsers :: forall m. (MonadBaseControl IO m, MonadIO m, MonadLogger m) => Migration (SqlPersistT m)Source