Easy Member
I rather enjoy making membership software.
When a long-time acquaintance expressed a need for an Easy Member solution, it was an excuse for me to make one. Easy Member is the result.
There are two PHP scripts to upload. Also a log-in form. And each member page gets a few lines of either PHP or JavaScript, depending on whether the member page can run PHP.
(In other words, the members area can work even with *.html
or other non-PHP web pages.)
It is easier to get an Easy Member site installed and set up than any other self-install membership software I've seen.
-
All settings, including the list of members, are on one dashboard page.
-
Member lists contain only usernames and passwords.
-
No MySQL database is used.
Passwords are respected.
-
Member passwords are automatically encrypted with one-way encryption.
-
After assigning a password, it is never again displayed in the dashboard (or anywhere else).
-
Because passwords are one-way encrypted, the plain text original can not be retrieved. To change a password, it must be completely replaced.
Here is a screenshot of the dashboard so you can see how easy it is to use.
The usernames and encrypted passwords are in a plain text file stored at a location on the server that you specify. Passwords are encrypted. To make the file (and the entire directory) even more secure, use the technique described in the Uncrackable Directory Block blog post.
So you know what to expect, the username/passwords file looks something like this.
mycookie 2 m https://example.com/pages/login.php will@example.com fffd86fb78a56a5145edsss9dcb00c78581ccae5 FriendlyFriend eef999fb78a56a5145ed7739dcb00c78581c544f totallyCool 8efd855578a56a5145ed7739dcb00c78581c5312
There is nothing you need to do with the contents of the file. If you view your own file, the first line contains the cookie and log-in URL information. The rest is lines of usernames and encrypted passwords.
Let's go ahead and install Easy Member.
Installing the Easy Member PHP Scripts
There are two PHP scripts to install. They both need to be in the same directory on your server. The directory must be accessible with a browser.
We will do the dashboard script first. Then the log-in script.
The dashboard script:
The dashboard script has two places to customize. Notes follow the source code.
<?php /* Easy Member Software - Dashboard Module - Version 1.0 September 19, 2020 Will Bontrager Software LLC https://www.willmaster.com/ */ /* *** Customizations *** */ // Two places to customize. // Place 1. /// Specify the location of the membership data file. // This must be identical to the location specified // in the EM_dashboard.php dashboard file. // The value specified here must be the same as the // $MemberDataFile specified in the Log In Module $MemberDataFile = "EMdata/EM.txt"; // Place 2. // Specify the username and the password for logging // into the dashboard. // The password may be encrypted. If not encrypted, // the password may not be exactly 40 characters // long. If encrypted, it muste be sha1 encrypted, // which is exactly 40 characters long. // The tool at // https://www.willmaster.com/secure/encrypt.php // may be used to encrypt the password. $Username = "change_login_credentials"; $Password = "8efd86fb78a56a5145ed7739dcb00c78581c5375"; /* *** Customization End *** */ mb_regex_encoding('UTF-8'); mb_internal_encoding('UTF-8'); ini_set('display_errors',1); error_reporting(E_ALL); if( ! ini_get('date.timezone') ) { date_default_timezone_set('UTC'); } $Global = array(); $Global['mcookiename'] = ''; $Global['mcookielife'] = ''; $Global['mlifeperiod'] = 'd'; $Global['mloginpage'] = ''; $Global['Members'] = array(); $Global['notice'] = array(); $Global['MemberDataFile'] = trim($MemberDataFile); $LoginForm = false; $Dashboardmcookiename = 'EM_dashboard_cookie'; $DashboardCookieExpire = time() + intval( 365.25 * 24 * 60 * 60 ); if( isset($_POST['login']) ) { if( isset($_POST['un']) and strlen($_POST['un'])>0 and isset($_POST['pw']) and strlen($_POST['pw'])>0 ) { $_POST['un'] = trim($_POST['un']); $_POST['pw'] = trim($_POST['pw']); $Username = strtolower($Username); $un = strtolower($_POST['un']); if( $Username != $un ) { $LoginForm = true; } else { if( strlen($Password) != 40 ) { if( $Password != $_POST['pw'] ) { $LoginForm = true; } } else { $sha1pw = sha1(trim($_POST['pw'])); if( $Password != $sha1pw ) { $LoginForm = true; } } } if( ! $LoginForm ) { $tm = time(); $_COOKIE[$Dashboardmcookiename] = $tm; setcookie($Dashboardmcookiename,$tm,$DashboardCookieExpire); } } else { unset($_COOKIE[$Dashboardmcookiename]); setcookie($Dashboardmcookiename,0,-1); $LoginForm = true; } } if( empty($_COOKIE[$Dashboardmcookiename]) ) { $LoginForm = true; } if( ! $LoginForm ) { if(strpos($Global['MemberDataFile'],'/')===0) { $Global['MemberDataFile'] = preg_replace('/^'.preg_quote($_SERVER['DOCUMENT_ROOT'],'/').'/','',$Global['MemberDataFile']); $Global['MemberDataFile'] = "{$_SERVER['DOCUMENT_ROOT']}{$Global['MemberDataFile']}"; } else { $Global['MemberDataFile'] = __DIR__ . "/{$Global['MemberDataFile']}"; } if( isset($_POST['updated']) ) { $Global['memholderlist'] = array(); ReadMemberData($Global['memholderlist']); # To get and hold any passwords belonging to members. $Global['notice'][] = 'Updating …'; $Global['mcookiename'] = preg_replace('/\W/','',$_POST['mcookiename']); $Global['mcookielife'] = intval(preg_replace('/\D/','',$_POST['mcookielife'])); $Global['mlifeperiod'] = preg_replace('/[^hdwmy]/','',$_POST['mlifeperiod']); $Global['mloginpage'] = trim($_POST['mloginpage']); $Global['Members'] = array(); foreach( preg_split('/[\r\n]+/',trim($_POST['memlist'])) as $line ) { $line = trim($line); if( preg_match('/ /',$line) < 1 ) { if( isset($Global['memholderlist'][$line]) ) { $Global['Members'][$line] = $Global['memholderlist'][$line]; } else { $Global['notice'][] = "New member username $line not recorded because no password provided."; } continue; } list($un,$pw) = preg_split('/\s+/',$line,2); $pw = sha1($pw); $Global['Members'][$un] = $pw; } WriteMemberData($Global['Members']); $Global['notice'][] = '… done.'; } else { ReadMemberData($Global['Members']); } # For dashboard page. } function ReadMemberData(&$memlist) { global $Global; if( ! file_exists($Global['MemberDataFile']) ) { return; } foreach( file($Global['MemberDataFile']) as $ln ) { $line = trim($ln); if( strpos($line,"\t")===false ) { $ta = preg_split('/\s+/',$line); if( count($ta)==2 ) { $memlist[$ta[0]] = $ta[1]; } } else { list($Global['mcookiename'],$Global['mcookielife'],$Global['mlifeperiod'],$Global['mloginpage']) = explode("\t",$line); } } } # function ReadMemberData() function WriteMemberData($memlist) { global $Global; $f = fopen($Global['MemberDataFile'],'w'); fputs($f,"{$Global['mcookiename']}\t{$Global['mcookielife']}\t{$Global['mlifeperiod']}\t{$Global['mloginpage']}\n"); foreach( $memlist as $k => $v ) { fputs($f,"$k $v\n"); } fclose($f); } # function WriteMemberData() ?><!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Easy Member Software Dashboard</title> <style type="text/css"> * { box-sizing:border-box; } html, body { font-size:100%; font-family:sans-serif; } .nowrap { white-space:nowrap; } .bold { font-weight:bold; } .italic { font-style:italic; } .underline { text-decoration:underline; } select { font-size:1em; } input, textarea { width:100%; font-size:1em; } input[type="text"], input[type="number"], input[type="password"], textarea { border:1px solid #999; padding:.25em; border-radius:5px; font-family:sans-serif; } #content { max-width:650px; margin:.5in auto; background-color:transparent; } </style> </head> <body><div id="content"> <form method="post" enctype="multipart/form-data" action="<?php echo($_SERVER['PHP_SELF']); ?>"> <div style="float:left; margin-right:1em;"><a href="https://www.willmaster.com/"><img src="https://www.willmaster.com/images/wmlogo_icon.gif"></a></div> <h1 style="padding-top:5px; margin-bottom:2em;">Easy Member</h1> <div style="clear:left;"></div> <?php if( count($Global['notice']) ): ?> <div style="border:3px double red; padding:1em; margin:.5in 0;"> <p class="bold">Notice:</p> <ul> <li><?php echo(implode('</li><li>',$Global['notice'])) ?></li> </ul> </div> <?php endif; ?> <?php if( $LoginForm ): ?> <h3> Log In </h3> <p> Username<br><input type="text" name="un"> </p> <p> Password<br><input type="password" name="pw"> </p> <p> <input type="submit" name="login" value="Log In"> </p> <?php else: ?> <h1 style="position:relative; top:-1em; font-weight:normal;">Dashboard</h1> <h3> URL of Member Log-in Page </h3> <p> The URL where members log in<br><input type="text" name="mloginpage" value="<?php echo($Global['mloginpage']) ?>" </p> <h3> Member Cookie </h3> <p> Member cookie name<br><input type="text" name="mcookiename" value="<?php echo($Global['mcookiename']) ?>" </p> <p> Member cookie expires in (0 or blank for expiration when browser is exited)<br><input type="number" name="mcookielife" style="width:4em; text-align:center;" value="<?php echo($Global['mcookielife']) ?>"> <select name="mlifeperiod"> <option value="h"<?php if($Global['mlifeperiod']=='h'){echo(' selected="selected"');} ?>>hours</option> <option value="d"<?php if($Global['mlifeperiod']=='d'){echo(' selected="selected"');} ?>>days</option> <option value="w"<?php if($Global['mlifeperiod']=='w'){echo(' selected="selected"');} ?>>weeks</option> <option value="m"<?php if($Global['mlifeperiod']=='m'){echo(' selected="selected"');} ?>>months</option> <option value="y"<?php if($Global['mlifeperiod']=='y'){echo(' selected="selected"');} ?>>years</option> </select> </p> <h3> Member List </h3> <p style="margin-bottom:3px;"> Specify member username and password sets separated with a space or tab — one set per line. Usernames, no spaces. Passwords may have any characters. When updating the member list, previously assigned passwords will be invisible and won't be changed unless you type something in for a new password. </p> <textarea style="height:2in;" name="memlist"><?php echo(implode("\n",array_keys($Global['Members']))."\n") ?></textarea> <h3> Update </h3> <p> <input type="Submit" value="Update Information" name="updated"> </p> <?php endif; ?> </form> <p style="margin-top:3em; border-top:1px solid #999; padding-top:.25em; display:table;">Copyright 2020 <a href="https://www.willmaster.com/">Will Bontrager Software LLC</a></p> </div> </html>
Notes —
There are two places to customize.
-
In the source code, you'll see:
$MemberDataFile = "EMdata/EM.txt";
Replace
EMdata/EM.txt
with the location for the membership data file.The location needs to be relative to the PHP script (no leading "/" character). A newly-created subdirectory of where the script is running is a good place to specify.
With the example
EMdata/EM.txt
in the above code, the membership data file is in theEMdata
subdirectory and the fileEM.txt
is the file name.Note: The very same value you specify for the file location in this script must also be specified in the log-in script further below.
-
Also in the source code, you'll see:
$Username = "change_login_credentials"; $Password = "8efd86fb78a56a5145ed7739dcb00c78581c5375";
Replace
change_login_credentials
with a username for your dashboard login.Replace
8efd86fb78a56a5145ed7739dcb00c78581c5375
with a password for your dashboard login. You may choose to specify one of two types of passwords.-
A plain text password that is either less than or more than 40 characters long — not exactly 40 characters.
-
A sha1-encrypted password (which is exactly 40 characters long). The form at https://www.willmaster.com/secure/encrypt.php may be used to encrypt the password.
-
When the dashboard script has been updated, save the file as easymemberDashboard.php
or other *.php
file name you prefer. (These instructions assume easymemberDashboard.php
is the file name.)
Upload easymemberDashboard.php
to your server and make a note of its URL — you will use the URL to access the dashboard.
The dashboard control panel
Verify that the subdirectory for the membership data file exists.
Then, put the URL to easymemberDashboard.php
into your browser and log in with the username and password you specified above.
Now, you'll fill in a few blanks.
-
URL of Member Log-in Page
Specify the URL of the web page that will contain the member log-in form.
-
Member Cookie
There are two sections here, the cookie name and how long the cookie shall stay in the browser.
-
The cookie name needs to begin with a letter. The rest of the name may consist of any alphanumeric characters and/or underscore characters.
-
Cookies can be specified to last any number of hours, days, weeks, months, or years. If you wish the cookie to expire when the browser is exited, specify 0 for the number field.
-
-
Member List
Specify at least one username and password so the log-in form can be tested. (The log-in form how-to is further below.)
The username may not contain any spaces.
The password may optionally contain spaces, and it may be as long as the person wants it to be (within reason; don't try a password as long as the content of the War and Peace book).
Tap the "Update…" button to store the control panel information.
Now, let's install the log-in script. (After that, the log-in form.)
The log-in script:
The log-in script has one place to customize. Notes follow the source code.
<?php
/*
Easy Member Software
- Log In Module -
Version 1.0a
October 10, 2020
Debug update, can now have only one member; v1.0 needed two or more.
Version 1.0, September 19, 2020
Will Bontrager Software LLC
https://www.willmaster.com/
*/
/* *** Customizations *** */
// One place to customize.
//
// Specify the location of the membership data file.
// This must be identical to the location specified
// in the EM_dashboard.php dashboard file.
// The value specified here must be the same as the
// $MemberDataFile specified in the Dashboard Module
$MemberDataFile = "EMdata/EM.txt";
/* *** Customization End *** */
mb_regex_encoding('UTF-8');
mb_internal_encoding('UTF-8');
ini_set('display_errors',1);
error_reporting(E_ALL);
if( ! ini_get('date.timezone') ) { date_default_timezone_set('UTC'); }
$MemberDataFile = trim($MemberDataFile);
if(strpos($MemberDataFile,'/')===0)
{
$MemberDataFile = preg_replace('/^'.preg_quote($_SERVER['DOCUMENT_ROOT'],'/').'/','',$MemberDataFile);
$MemberDataFile = "{$_SERVER['DOCUMENT_ROOT']}$MemberDataFile";
}
else { $MemberDataFile = __DIR__ . "/$MemberDataFile"; }
$mcookiename = $mcookielife = $mlifeperiod = $mloginpage = false;
$Members = array();
if( file_exists($MemberDataFile) )
{
foreach( file($MemberDataFile) as $ln )
{
$line = trim($ln);
if( strpos($line,"\t")===false )
{
$ta = preg_split('/\s+/',$line);
$Members[$ta[0]] = $ta[1];
}
else { list($mcookiename,$mcookielife,$mlifeperiod,$mloginpage) = explode("\t",$line); }
}
}
if( count($Members) < 1 )
{
echo '<p>Missing or blank data file, or unable to find members in it.</p>';
exit;
}
$match = false;
$memname = strtolower(trim($_POST['un']));
$mempass = sha1(trim($_POST['pw']));
foreach( $Members as $n => $p )
{
if( $memname == strtolower($n) )
{
if( $p == $mempass )
{
$match = true;
break;
}
}
}
if( ! $match ) { GoToNewPagePHP($mloginpage); }
$expires = 0;
$i = 24 * 60 * 60;
switch($mlifeperiod)
{
case 'y' : $expires = ($i * 365.25); break;
case 'm' : $expires = ($i * 30.5); break;
case 'w' : $expires = ($i * 7); break;
case 'd' : $expires = $i; break;
default : $expires = 60*60;
}
$expires *= $mcookielife;
if( $expires )
{
$expires = time()+intval($expires);
setcookie($mcookiename,time(),$expires);
}
else { setcookie($mcookiename,time()); }
GoToNewPagePHP( isset($_POST['url']) ? $_POST['url'] : '/' );
function GoToNewPagePHP($url)
{
if( headers_sent() ) { echo "<script>location.href='$url';</script>"; }
else { header("Location: $url"); }
exit;
}
?>
Notes —
There is one place to customize.
-
In the source code, you'll see:
$MemberDataFile = "EMdata/EM.txt";
Replace
EMdata/EM.txt
with the same location for the membership data file as you specified in theeasymemberDashboard.php
dashboard script further above.
When the log-in script has been updated, save the file as easymember.php
or other *.php
file name you prefer. (These instructions assume easymember.php
is the file name.)
Upload easymember.php
to your server into the directory where the easymemberDashboard.php
dashboard script is located. Make a note of its URL — the log-in form will need it.
The Log-in Form
Here is source code for a log-in form. Notes follow.
<form method="post" action="https://example.com/mem/easymember.php"> <input type="hidden" name="url" value="https://example.com/pages/page.php"> <p> Username <input type="text" name="un" style="width:200px;"> </p><p> Password <input type="password" name="pw" style="width:200px;"> </p><p> <input type="submit" value="Log In" style="width:200px;"> </form>
Notes —
There are two places to customize.
-
In the form source code, replace
https://example.com/mem/easymember.php
with the URL to your installation of theeasymember.php
script. -
In the form source code, replace
https://example.com/pages/page.php
with the URL to the landing page in your member area.
Put the form into a web page so members can log in.
The form may be designed as you wish. But the form field names need to have the same values as they have in the above source code.
Load the web page with the log-in form in your browser. Log in with the username and password you specified earlier in the Easy Member dashboard.
Individual Member Pages
Each member page gets a few lines of code to check the member cookie when the page loads into a browser.
The code could be PHP code or JavaScript code. Use the JavaScript code only if you can't use the PHP code.
The reason to use the PHP code when you can is because the JavaScript code won't work for robots and for browsers with JavaScript turned off.
The PHP code:
This PHP code needs to be in all member pages, preferably at the top of the page so no content is sent to the browser when the member cookie is absent.
In the following code, replace https://example.com/pages/login.php
and mycookie
with, respectively, the log-in page URL and the cookie name that you specified in the Easy Member dashboard.
<?php $loginURL = "https://example.com/pages/login.php"; $cookiename = "mycookie"; if(empty($_COOKIE[$cookiename])) { if( headers_sent() ) { echo "<script>location.href='$loginURL';</script>"; } else { header("Location: $loginURL"); } exit; } ?>
The JavaScript code:
When PHP code can't be used, JavaScript can suffice. But be aware that robots and browsers with JavaScript turned off will have access to the member page.
The JavaScript code needs to be in all member pages, preferably as close to the top of the page as you can get it. The top-of-page reason is for both speed and so no or little content is sent to the browser when the member cookie is absent.
In the following code, replace https://example.com/pages/login.php
and mycookie
with, respectively, the log-in page URL and the cookie name that you specified in the Easy Member dashboard.
<script style="text/javascript"> function Easy_Member_Monitor() { var loginURL = "https://example.com/pages/login.php"; var cookiename = "mycookie"; if(document.cookie.indexOf(cookiename)<0){location.href=loginURL;} } Easy_Member_Monitor(); </script>
Using Easy Member
Now that the member software is installed, simply add and delete member usernames and passwords in the Easy Member dashboard as appropriate for your needs.
If you add member pages, remember to add the few lines of PHP (or JavaScript) to verify the cookie exists.
(This article first appeared with an issue of the Possibilities newsletter.)
Will Bontrager