User Login

A User login system built with Liaison, which can be plugged into any existing website, whether you use Liaison or not.

Legal: Terms & Conditions are provided within, but the developer of this software is not liable for your use of it, or for the terms provided. YOU, the user of this software are fully liable for all legal matters pertaining to your use of this software.
Security: You are liable for all security implications in using this software. I take no responsibility for the security, even for documented security features. It is your responsibility to review the software before using it to ensure that it meets your security requirements.

Under development!

Just bumped to v0.4 from v0.3 and will be adding new features & possibly introducing some (hopefully not many) breaking changes. Now depends on Liaison v0.7

Documentation (all below)

  • Features
  • Install
  • Server Setup
  • Get User
  • Roles & Permissions
  • Styling / CSS
  • Page Layout / Theme
  • Disable Specific Pages within the user library (like the register page)
  • Programmatically register a user
  • User Dashboards
    • Add Pages to the Dashboard
    • Dashboard Hook
  • Configuration
    • Available configurations
    • Choosing an email service
  • Other
  • Notes, Troubleshooting

Docs TODO:

  • adding the css to your <head>

Features (ALPHA)

  • login, password reset, registration
  • roles & permissions (managed programmatically)
  • CSRF and XSS prevention, honeypots and throttling for bot prevention, security logging
  • Built-in Terms & Conditions (WHICH YOU SHOULD REVIEW BECAUSE YOU ARE LIABLE FOR ALL LEGAL MATTERS)
  • Fairly robust testing (I think)
  • GUI for initial setup of your first user & initialization of db tables
  • User Dashboard

Bad or missing features

  • 2FA (none)
  • GUI for roles & permissions (none)
  • automatic honeypot testing
  • CLI (the bin/tlfuser script is for recompiling SQL files in the library, so only for development).
  • profile pages

Install

composer require taeluf/user-gui v0.4.x-dev   

or in your composer.json

{"require":{ "taeluf/user-gui": "v0.4.x-dev"}}  

Server Setup

EasyServer is a wrapper class for easy setup of the user library. For a more custom setup, see EasyServer for code to copy from and modify.

  1. Create a file user-initialization-pin.txt and put in a random string that is between 20 and 40 characters long.
  2. (optional) create user-config.json to store configurations. This will be overwritten when it is modified through GUI.
  3. Edit the below script as needed: Setup your PDO connection, update the file paths, handle page layout
  4. Visit /user/initialize/ with your user initialization pin handy, and fill out the form to (optionally) initialize the DB & to setup your initial user with the role 'admin' and no permissions.
  5. user-initialization-pin.txt is deleted before any changes are persisted, and the initialization page only functions when there are no users in the database.
<?php // remember to autoload!  
  
$pdo = new \PDO(...); // initialize a connection to a MySQL database  
  
if (substr($_SERVER['REQUEST_URI'],0,6)=='/user/'  
    ||substr($_SERVER['REQUEST_URI'],0,14) == '/lia-resource.' // for styles to load  
){   
    // this file will be deleted automatically, unless an error occurs  
    $initialization_pin_file = __DIR__.'/user-initialization-pin.txt';  
    $config_file = __DIR__.'/user-config.json';  
  
    $lia = new \Lia(); // a framework named Liaison  
    $main = \Lia\Package\Server::main($lia); // a built-in package & set of addons  
    $userServer = new \Tlf\User\EasyServer($lia, $initialization_pin_file, $config_file, $pdo);  
    // $userServer->get_user_lib(); // \Tlf\User\Lib, the central component of this user library  
    // If you want the library to handle head,body, etc, then uncomment the next three lines & delete the rest. It's not styled.  
    // $userServer->enable_full_page();   
    // $lia->deliver();   
    // exit;  
      
    $response = $lia->getResponse();  
  
    if (count($response->headers) > 0){  
        $response->sendHeaders(); // redirects, maybe errors idr  
        exit;  
    } else {  
        // You might instead print the content inside your page layout.  
        echo $response->content;  
        exit;  
    }  
}  

Get a User

<?php  
  
$pdo = new \PDO();// setup a mysql connection  
$lib = new \Tlf\User\Lib($pdo);    
  
// get the currently logged in user or an anonymous unregistered user if no user is logged in.  
$user = $lib->user_from_cookie();   
if ($logged_in_user == false)$logged_in_user = new \Tlf\User($pdo);  
  
// Get a user by their email address  
$user = $lib->user_from_email('reed@example.com');    

Roles & Permissions

Permissions & Roles are managed programattically, for now.

Roles are named groups of users and permission sets. Permissions are used to represent specific actions, such as creating new blog posts or modifying site headers

Some websites may just use roles (such as 'admin') to control access and not worry about individual permissions.

<?php  
$pdo = $this->pdo();    
$lib = new \Tlf\User\Lib($pdo);    
// user MUST be registered already  
$user = $lib->user_from_email('reed@example.com');   
  
## Roles on Users  
$user->add_role('admin'); // admin dashboards and admin pages within the user lib check for the 'admin' role.  
$has_role = $user->has_role('admin'); // true  
$user->remove_role('admin');  
  
## Permissions for a user  
$user->allow('blog:create');  
$can_create_blog = $user->can('blog:create');  
$user->deny('blog:create');  
// WARNING: deny deletes the permission, so if a role enables a permission, that allowance will still work.  
  
## Permissions for a role  
$lib->role_allow('blogger', 'blog:create');  
$lib->role_allow('admin', 'blog:create');  
if ($user->can('blog:create')){} // this works if user has role 'blogger' or 'admin' now  
  
$lib->role_deny('blogger', 'blog:create');  
// WARNING: deny deletes the permission from the role, so if a user is otherwise permitted to blog:create, that allowance will work.  
  
$lib->role_delete('blogger');  
// NOTICE: Deletes all entries for the role. No user will have the role 'blogger' any more, and no permissions will exist on the role 'blogger'.   
  

Styling / CSS

CSS Files are managed by Liaison, with the Resources Addon. You can sort, remove, or add new files. See Resources Documentation.

CSS Files in this library:

  • src/view/form/login.css
  • src/view/form/register.css
  • src/view/form/dashboard.css
  • src/view/form/request.password_reset.css
  • See src/view/ in case this list is out of date.

Removing Built-in CSS Files - You will implement a sorter function, like:

<?php  
function(array $files): array {   
    $dashboard_css_file =   
        __DIR__   
        . '/vendor/taeluf/user-gui' // this library's path if using composer  
        . '/src/view/dashboard.css'; // path to the dashboard.css file.  
  
    $index = array_search($dashboard_css_file, $files);  
    if ($index !== false){ // just in case you messed up the path  
        unset($files[$index]);  
        return $files;  
    }  
    throw new \Exception("Failed to remove dashboard.css file.");  
}  

Page Layout / Theme

Theme can be managed on the built-in server addon.

  
    // tell Liaison not to use the page layout / theme  
    $lia->addon('lia:server.server')->useTheme = false;  
  
    $response = $lia->getResponse();  
    $headers = $response->headers;  
    $content = $response->content;  
  
    if (count($headers) === 0){  
        display_content_within_my_custom_theme($content);  
    } else {  
        // redirects  
        $response->sendHeaders();  
        exit;  
    }  
  

Disable Specific Pages

Do this, but remove any that you want to leave enabled.

$lib->disabled_pages = [  
    'login', 'register', 'reset-password', 'logout', or 'terms'  
];  

Programmatically register a user

<?php  
$user = $lib->user_from_email('reed@example.com');    
$user->register($submitted_or_random_password);  
$code = $user->new_code('registration'); // as opposed to a 'login_cookie' or 'password_reset'  
$user->activate($code); // because we're not sending them the registration email with the activation link  
// $user->add_role('admin'); // You might do this for your first user.  

User Dashboard

Left-hand sidebar menu that shows content from various pages that have been added to the dashboard.

Warning: Any logged in user can access their dashboard. You must check user access BEFORE adding pages to the dashboard.

Add Pages to the Dashboard

  1. Create a view accessible by Liaison
    • in your a registered view dir
    • or $lia->addon('lia:server.view')->addViewFile('view_name', 'path/to/file.php'), file.php should output content
    • or $lia->addon('lia:server.view')->addViewCallable('view_name', function(string $view_name, array $args){ return 'content';});
    • or $lia->addon('lia:server.view')->addView('view_name', '/views/directory/')
  2. Ensure the logged in user is permitted to access the dashboard you're adding. (See above)
  3. Call $easy_server->add_user_dashboard(string $name, string $view_name)
  4. Optionally hook into \Tlf\User\Hooks::DASHBOARD_DISPLAYED

Dashboard Hook

Simple approach, url check:

<?php  
if (substr($_SERVER['REQUEST_URI'],0,strlen('/user/dashboard/'))=='/user/dashboard/'){  
    // may contain css to switch to a full-width page to better display the dashboard  
    $lia->addResourceFile(dirname(__DIR__).'/file/user-dashboard.css');  
    // or add css to the header however you like  
}  

Through Liason. Hook is called after the dashboard content is loaded into memory.

<?php  
  
// Alternatively call directly on the hook addon  
// $lia->addon('lia:server.hook')->add(DASHBOARD_DISPLAYED, function(){});  
$lia->hook(\Tlf\User\Hooks::DASHBOARD_DISPLAYED,  
    function(\Lia $lia, \Tlf\User\EasyServer $server){  
        // may contain css to switch to a full-width page to better display the dashboard  
        $lia->addResourceFile(dirname(__DIR__).'/file/user-dashboard.css');  
    }  
);  

Configuration

  1. EasyServer is instantiated with a path to a json config file.
  2. This config file will contain all but your most sensitive settings.
    • Some settings MUST be set programmatically (in the config file, or in php)
    • Other settings are configurable through the User Dashboard.
  3. Sensitive settings (like smtp password) should be in your ENV.
  4. After instantiating, some settings should be set programmatically, and others cannot be changed via $lib->config:
    • $lib->config[C::smtp_password] = $_ENV['user.smtp_password'];
    • $lib->mail_service = \Tlf\User\MailService::DEBUG_TO_TEXTFILE;
    • $lib->mail_service_callable = function (...){}; (Alternate: config file may point to a declared callable)
    • Base URL for User Lib: $lia->set('user.base_url', '/user-login/'); (default is /user/)

Each of these can be configured in your user config json file:
(See Configurations.php for up-to-date copy of this)

  
<?php  
    ##### programmatically configurable #####  
          ## Programattically configurable options can be set in the user-config.json, but they are not editable from the admin dashboard  
  
    // default is `/user/`.  To set after instantiating EasyServer, use `$lia->set('user.base_url', '/user-login/');`  
    const base_url = 'user.base_url';  
  
    /** Array, choose from 'login', 'register', 'reset-password', 'logout', or 'terms' */  
    // default is []  
    const disabled_pages = 'user.disabled_pages';  
    // default is MailService::PHP_MAIL  
    const mail_service = 'user.mail_service';  
    // used for MailService::CUSTOM_CALLABLE. No default  
    const mail_service_callable = 'user.mail_service_callable';  
  
    ##### user configurable #####  
    const web_address = 'user.web_address';  
    const email_from = 'user.email_from';  
    const name_from = 'user.name_from';  
  
    // Used for MailService::LIB_PHPMAILER   
    const smtp_host = 'user.smtp_host';  
    /** It's best to set this programmatically from your Environment settings & leave this blank in user-configs */  
    const smtp_password = 'user.smtp_password';  
?>  

Choosing an email service

Settings above will need to be configured as well.

These can be set via your user config json file, or programmatically.

  1. Set user.mail_service to one of: (See MailService)
    • MailService::PHP_MAIL / php_mail: Uses PHP's mail() function
    • MailService::CUSTOM_CALLABLE / custom_callable: Same method signature as mail()
    • MailService::LIB_PHPMAILER / lib_phpmailer: Uses PHPMailer library.
    • MailService::DEBUG_TO_TEXTFILE / debug_to_textfile: Output email body to vendor/taeluf/user-gui/email-body-out.txt instead of sending.
  2. Setup Dependencies & Additional Settings
    • PHP_MAIL: none
    • CUSTOM_CALLABLE: Configurations::mail_service_callable
    • LIB_PHPMAILER: install via composer require phpmailer/phpmailer. smtp_host & smtp_password must be configured.
    • DEBUG_TO_TEXTFILE: none

Notes

  • Timezone: Set mysql timezone to UTC, with SET GLOBAL time_zone = '+00:00';

Testing

  • Run Tests: phptest or venor/bin/phptest
    • Running tests DROPs every table & recreates them, prior to each test class being run.
  • Test Server: phptest server main or phptest server init (many tests depend on the 'main' test server)
    • user-configs.json is replaced each time the main server is started. Multiple files are replaced when init is started.
  • Create Sample users: phptest -test TestUsers (the test output gives you username & password)
  • Visit /user/ in your browser and navigate to test pages.
  • Setup db credentials in test/db-env.json. Use test/db-env-example.json as a template.
  • For additional testing, see below.

Manual Initialization & Admin Testing

  • 2025-01-21: Initialization testing is only manual, not automated
  • Each time you launch the init server, the user-configs.json, deliver.php, and initialization-pin.txt files are reset.
  1. Delete your user database & recreate it (but do not recreate tables). (drop database temp_user; create database temp_user;)
  2. phptest server init to run Initialization Test Server
  3. You will see output in the terminal 'Wrote initialization-pin.txt ...' Copy the pin. (In production, this should be far more random and alphanumeric+symbols, and freshly generated for each initialization)
  4. Visit /user/ and you should get an exception in your server logs telling you that only /user/initialize/ can be visited.
  5. Visit /user/initialize/
  6. check 'initialize database', paste the initialization pin, and enter an admin user email & password, then submit. (Real email recommended, because the initialization test server allows you to configure your mail service, and test actual sending of emails.)
  7. Visit /user/initialize/ to ensure that the page is now disabled. (Your server logs should display an exception "Initialization pin file does not exist. ....)
  8. Visit /user/ and you should get an exception in your server logs telling you that only /user/initialize/ can be visited.
  9. edit test/InitServer/deliver.php and delete the line $user_package->enable_risky_web_initialization(__DIR__.'/initialization-pin.txt'); (deliver.php is re-created each time you run phptest server init)
  10. Visit /user/initialize/ and you should see in your server logs that no routes were found.
  11. Visit /user/ and verify that you can login, and use the dasbhoard, and that you have the admin role.

Manual GUI Testing (non-admin)

  1. Start main (phptest server main)
  2. Run a test to initialize the database (phptest -test InitDb)
  3. Visit /user/ and create a new account (whatever email & password you want. No actual emails will be sent. They go to a debug output file.)
  4. Open email-body-out.txt at the root of this library and copy+paste the URL into your browser to complete registration.
  5. Login with your account. This is not an admin account. There are no pre-configured roles or permissions, so you should not have access to any admin pages or admin dashboards.

Playground Server

This is NOT used for any automated testing. It is for playing around with and modifying things, while not having to worry about leaving a consistent state.

  1. phptest server playground
  2. Follow Manual GUI Testing instructions, i guess.