[web-devel] How to use Yesod.Auth.HashDB?

Michael Lazarev lazarev.michael at gmail.com
Fri Feb 10 23:59:17 CET 2012

Hello everybody!

In one app I develop, users will not register by themselves. Instead,
they will be added to the system and assigned roles by administrator.
For that case, HashDB auth plugin looked the right choice for me.

TL;DR: The setup proposed in Yesod.Auth.HashDB documentation didn't
work for me. Finally I found the solution. I wonder if putting User
entity definition in Yesod.Auth.HashDB module is a good idea.

There are three reasons I post this: to publish my solution for
anybody having the same problem, to ask a question if I am doing it
right, and, last but not least, to submit a feedback to Yesod authors.

In a freshly scaffolded sqlite-based setup made with Yesod-0.10.1, I
did changes recommended in Yesod.Auth.HashDB haddock.
See these instructions here:

Here you can see how I applied these instructions to my project:
https://gist.github.com/1793058 . That led to errors shown in the same
gist below.

These errors do make sense: if User is defined in Yesod.Auth.HashDB, I
have to delete the duplicate of definition from my config/models file.
But then I get another error: `Model.hs:13:1: Not in scope: type
constructor or class `UserGeneric'`

Ok, fair enough again. Because now I don't define User entity in
config/models, Model.hs must take it from HashDB module.

Let's add `import Yesod.Auth.HashDB` to Model.hs file. This fixes the
error, but then another one appears:

    Couldn't match expected type `FCCS -> [AuthPlugin FCCS]'
                with actual type `[t0]'
    In the expression: [authHashDB (Just . UniqueUser)]
    In an equation for `authPlugins':
        authPlugins = [authHashDB (Just . UniqueUser)]
    In the instance declaration for `YesodAuth FCCS'

Ok, the underscore must be added after definition of authPlugins
instance method. Of course, it should be there, even if it's missing
in example on top of Yesod.Auth.HashDB haddock.

Now, another error:

Devel application launched, listening on port 3000
devel.hs: Table not found: User
Exit code: ExitFailure 1

Table not found. I checked the database, there is "user" table there.
What can this be? Let's swap these lines in Application.hs
Now they will be like this:

Database.Persist.Store.runPool dbconf (runMigration migrateUsers) p
Database.Persist.Store.runPool dbconf (runMigration migrateAll) p

Before they were in reverse order.
Let's see that happens:

Devel application launched, listening on port 3000
Migrating: CREATE TABLE "User"("id" INTEGER PRIMARY KEY,"username"
NULL,CONSTRAINT "UniqueUser" UNIQUE ("username"))
devel.hs: user error (SQLite3 returned ErrorError while attempting to
perform prepare "CREATE TABLE \"User\"(\"id\" INTEGER PRIMARY
KEY,\"username\" VARCHAR NOT NULL,\"password\" VARCHAR NOT
(\"username\"))": table "User" already exists)
Exit code: ExitFailure 1

Other error, but no luck yet.
What if we drop the table "User"?

Migrating: CREATE TABLE "User"("id" INTEGER PRIMARY KEY,"username"
NULL,CONSTRAINT "UniqueUser" UNIQUE ("username"))
devel.hs: Table not found: User
Exit code: ExitFailure 1

What if we delete the database altogether? `devel.hs: Table not found: User`
Of course it is not found. But what can I do to fix this?
Surely I don't know what I am doing here. Any clarifications?

And then it occurred to me that even if I managed to get it working
that way, this still will be not quite right.
What if I will decide to add some fields to User entity later? I can't
do it if User is exported from HashDB.
And I really plan to add one field just after I make this work. The
"role" field to manage permissions.

So, instead, I copied the definition from HashDB source to my config/models:

     username Text Eq
     password Text
     salt     Text
     UniqueUser username

And then copied the instance definition to Model.hs:

 instance HashDBUser (UserGeneric backend) where
   userPasswordHash = Just . userPassword
   userPasswordSalt = Just . userSalt
   setSaltAndPasswordHash s h u = u { userSalt     = s
                                    , userPassword = h

To eliminate conflicts, in Model.hs the import was restricted to
`import Yesod.Auth.HashDB (HashDBUser(..))`

In Application.hs it was removed along with migration command, and
Foundation.hs it was narrowed to `import Yesod.Auth.HashDB
(authHashDB, getAuthIdHashDB)`

Finally, it worked. You may want to look at
https://gist.github.com/1793594 for the set of changes needed to
achieve this.
Anyway, I can't get rid of the feeling that there is some "right"
method to do this, that I either overlooked, or couldn't come up with
myself. Any advice?

More information about the web-devel mailing list