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:
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:
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:
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:
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.
Can I also add a session cookie?
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..