Creating a Todo-App - 5: Adding the User Login

Creating a Todo-App - 5: Adding the User Login

·

5 min read

In the last post we discussed the theory of using the adm.l library to add a simple user login field. Today we will show how to do that in our app.


Creating the Login Form

The global variable *Login stores whether a user is logged into the session. Therefore we can simply wrap a ifn *Login around the whole code that we do not want to display, and add a "Username-Password" field. With these values, we call the login function if the button is pressed. If the user cannot be authenticated, the page is reloaded with a "Permission Denied" error with the error function:

(ifn *Login
   (form NIL
      (gui 'nm '(+Focus +TextField) 20) # name
      (gui 'pw '(+PwField) 20)                # password
      (gui '(+Button) ,"Login"
         (if (login (val> (: home nm)) (val> (: home pw)))
            (url "!todoList")
            (error ,"Permission denied") ) ) ) )

With a little bit of styling, it looks like this:

loginfield.png


Creating the user

Of course we cannot really login yet because we didn't create a user. We can create one in the REPL:

% pil todo-user-login.l -main +
: (request '+User 'nm "admin" 'pw (passwd "admin"))
-> {433}
: (commit)
-> T

Now let's restart the application. We can now login with "admin", password "admin".


Logout button

Let's also add the logout button. It's easy:

(form NIL
   (gui '(+Button) T "img/logout.png"
      '(prog
         (post (logout)) ) ) )

Now we have a simple page where a user can log in and out. You can find the source code of this example here.


Adding Roles

Now let's add roles to our model. We open the database again and define two roles, "guest" and "admin". "guest" has the permission "view" while the "admin" should be able to edit the list.

: (request '(+Role) 'nm 'admin 'perm '(view editList))                                                                                                                                              
-> {466}   
: (request '(+Role) 'nm "guest" 'perm '(view))                                                                                                                                                             
-> {503} 
: (commit)
-> T

We add the "admin" role to the admin user and the "guest" role to the guest user.

: (put!> '{433} 'role '{466})    # admin user, admin role
-> {466} 
: (put!> '{501} 'role '{503})     # guest user, guest role                                                                                                                                                         
-> {503}

You can double-check it with help of the select-predicate:

: (select +Role)                                                                              
{466} (+Role)                                                                                 
   nm admin                                                                                   
   usr ({433})                                                                                
   perm (view editList)                                                                            

{503} (+Role)                                                                                 
   nm "guest"                                                                                 
   usr ({501}) 
   perm (view)

Set viewing permission with must

Up to now, we only had a single function to be executed: todoList. However, now we want to make the login form accessible to everybody, while todoList should be restricted. Therefore let's divide the source into two functions like this:

Login Form: This is the function that gets called now when the page is visited. We need to add it to the allowed and the server functions as well.

(de userLogin ()
   (app)
   (action
      (html 0 "Todo" *Css "todo-body"
           # ... Login Form comes here ...
                     (gui '(+Button) ,"Login"
                        '(ifn (login (val> (: home nm)) (val> (: home pw)))
                           (error ,"Permission denied")
                           (url "!todoList")  ) ) ) ) ) ) ) ) )

Todo List: This one gets restricted with must:

(de todoList ()
   (must "view permission is missing!" view)
   (app)
   (action
      (html 0 "Todo" *Css "todo-body"
         # ... the rest comes here ...

Now let's see what happens if we try to access the "todoList"-page without being logged in. As expected, we get a "No Permissions" response:

nopermission.png

On the terminal, the following line gets logged:

17141 / <DATE> <TIME>
17141 No permission: view permission is missing!

Setting the edit permissions with may

While guest and admin should both be able to view the page, only the admin should be able to edit them. For this purpose we use may in the +Able prefix class of the "delete" button. In order to make it more clear, we also change the icon of the disabled button:

(gui 3 '(+Style +JS +Able +Button) 
   "button-icon" 
   '(and
      (may editList) 
      (nth (: chart 1 data) (row)) ) 
   T "img/delete.png" "img/delete-disabled"
      '(prog 
         (lose!> (curr))
         (init> (: home query)) ) ) ) ) ) )

Now the delete button is disabled when the row is empty or when the permission is not available.

This is how it looks like for the admin user:

adminview.png


Let's do the same thing for the "add new items" button. But instead of hiding only the button, we can hide the whole form field:

(if (may editList)
   (<div> NIL
      (gui '(+Cue +TextField) "Thing to do" 30 )
      (gui '(+Cue +DateField) "Enter a date" 10 )
   ... )

Now it looks like this for the "guest" user:

guestview.png

Only the admin user is able to add and delete items, while the guest can only view.


The final source code can be downloaded here.


Like described in the first post about HTML forms, there might be applications where the developer/user/customer is unhappy with the authentification via the session only. So can we also add a session cookie on top of the current session?

In principle, no problem: cookies can be set using the cookie function, for example during the authentification:

(cookie 'key 'val)

Typically, the session key is a random string which sometimes also contains information about the role model (admin/guest/ etc.). This key needs to have an expiry date and gets checked at every transaction. One disadvantage is that you can only permit one single session per browser (because the browser cannot differentiate between the sessions), while in the current system the number of sessions is unrestricted.

So: Yes, it is possible to add a session cookie, but like with all security-sensitive topics it requires some attention to create a clean design. Maybe I will create an example for a later post.


The source codes of this post can be downloaded here:


Sources

Freepik from www.flaticon.com
developer.mozilla.org/en-US/docs/Web/HTTP/C..