|Did you know ...||Search Documentation:|
|Pack identity -- README.md|
An identity management system for SWI-Prolog.
This release has all basic functionality working.
Testing has been minimal, but if you're adventurous, this is now the reasonable way to make a login and registration system in SWI-Prolog.
Almost every web application has the notion of a user, with an account, a profile, and some privileges.
And signing up for and maintaining this is pretty much done the same way, with slight variations, everywhere.
And it can get messy and complex to do. There's no reason for everybody to do it over and over.
I needed a library like this, so I wrote one.
Almost everything in this library might need customizing. I will try to be good about promptly accepting PRs for needed hooks. Please contact the lead maintainer (Anniepoo on github, firstname.lastname@example.org by email) before implementing such so we can maintain some uniformity.
And forking and hacking on this or just stealing parts are valid.
This software depends on SWI-Prolog 8.1.0 or later.
This library is definitely alpha software. It's currently close enough to production ready that you could contribute a few days work and it would be ready for beta use.
A license file should accompany this software. The intent is that this software should be easily integrated with SWI-Prolog license.
Portions of this software incorporate code from the demo_login.pl example
package-http, part of SWI-Prolog.
This is a cookbook guide to getting identity services running.
You will need to ensure the identity library is loaded.
If you're reading this document you probably already did this.
Now you can load identity.
We won't need sessions, generally, for users not logged in.
Set your session options to
create(noauto) prior to starting the server.
You might also want to increase the session time.
http_set_session_options( [ create(noauto), timeout(1800) % half hour sessions ]),
http_server(http_dispatch, [port(5000)]). ---
You will need the normal js, css, and img handlers. You can either just
library(identity/login_static) and let
pack(identity) handle it, or
set them up yourself.
As long as the abstract paths
at the appropriate directories you'll be fine.
Identity is agnostic about how you store your user data.
A simple choice is the default back end. Call
use_default_db before starting the server.
User data will be persisted to the file users.db using
This solution is adequate for 100,000 users or so.
If you need something else,
library(identity/login_database) provides this set of multifile predicates.
Each takes the arguments UserName and a compound. They make a key-value store in the obvious way.
All second arg values are compounds. Currently all are of arity 1.
At minimum this set of compounds should be supported.
password_hash(Hash)- A cryptographically secure pw hash
email(Email)- should be per-user unique
activation_key(Key)- one of poss. several valid keys to email activate the account
If at all possible storing arbitrary functors should be supported, as the registration form roadmap includes adding arbitrary other info. Any functor may appear multiple times. eg a user might have multiple roles. This store is a convenient place to store other per_user data.
These user roles are known.
When a user has logged in, completed 2FA, activated their account, isn't banned, and so on, they
role(user). They may have additional roles (admin, teacher, ...).
Add a role/1 option to any handler that requires login. This can be any
roles you want. A common set would be [user, admin], and most 'normal' pages would be
Non logged in users can access only pages without a role.
Default behavior is to only make a session when a user logs in. If you need sessions for guests I suggest you figure out what that should look like and send us a PR.
http_handler(root(secret), secret_handler, [id(secret), role(user)]).
By default, all login happens on URI path `/login`. To move it, assign a higher priority user:location in another place
:- multifile http:location/3.
location(login, root(mylogin), [priority(0)]).
If your user doesn't have access to the page, they will be directed to an
error page. See the error section of `
library(identity/identity) for the default no-access page. If you would
like to create your own status page, copy this code and ensure it's loaded after
the identity pack is loaded.
Set the setting
identity:style to style pages. The default is
Currently, the `.warning`, `.warn`, and `.error` classes are set with a style block at the end of the registration page. This is likely to change soon.
Most strings displayed to the user pass through local/2.
By implementing the multifile local_hook/2 you can alter most user messages. Return atoms.
You can make a higher priority version handler for any page.
In a manner similar to overriding the login form handler, you can override
login(register) to make a custom registration page. Additional fields in
the registration form are persisted into the user info database
You can make logout links by simply visiting `/login/logout` while logged in.
By default this returns to the id
home. If you want to go elsewhere, add a parameter
The inclusion current_user//0 can be used in termerized html to display the user name.
To require email account activation set setting identity:require_activation_email to true.
Because there are many possible ways to send email and one usually customizes the email body, actually sending the email is left to the application developer.
login_email:activation_email_hook(UName, Email, Link), sending the email.
If not implemented the default sends the link to debug/3. This can be useful for debugging.
Currently not done, but working on password reset email. A similar hook will be provided.
validation is controlled by a nested dict. This dict is a setting,
login_validate.pl for the default. Other than using messy regexes, it's fairly obvious.
I intend to add ajax form verification at some point.
These are not yet addressed.
We really just need a list of things that must be true, a list of goals, some that share variables,
and we start at the front and call each. If it fails, we call
make_true(Goal), and that interacts with the user
to make the thing true.
But the list of goals could change (eg if you allow pw reset you must setup the backend email server),
so maybe it's more like an adventure game, where you must walk through the map, and it presents choices
and has planner like actions. Then automatically take the action when only one is possible.
Alternatively, we could have 2 phases - first we ask the user what they want to do, and then we copile what they need to do, and guide them through doing it. And maybe they can save their work in the second part.
requiring a role