JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
upgrade login/password/session/auth handling
authorJason Woofenden <jason@jasonwoof.com>
Tue, 23 Jun 2015 00:17:30 +0000 (20:17 -0400)
committerJason Woofenden <jason@jasonwoof.com>
Tue, 23 Jun 2015 00:17:30 +0000 (20:17 -0400)
15 files changed:
admin.html
admin.php
admin_admins.html [deleted file]
admin_admins.php [deleted file]
admin_admins.sql [deleted file]
admin_files.php
admin_images.php
admin_login.html [deleted file]
admin_login.php [deleted file]
admin_pages.php
config.php
inc/cms.php
inc/session_auth.php [new file with mode: 0644]
login.html [new file with mode: 0644]
login.php [new file with mode: 0644]

index c60e53f..cce5a90 100644 (file)
@@ -15,7 +15,7 @@
 
                <p><a href="admin_files">Manage (downloadable) files</a></p>
 
-               <p><a href="admin_admins">Manage administrators (passwords, etc.)</a></p>
+               <p><a href="admin_users">Manage accounts (admin passwords, etc.)</a></p>
 
                <p><a href="logout">Log out</a></p>
        <!--~}~-->
index f78b84b..ed40e7a 100644 (file)
--- a/admin.php
+++ b/admin.php
@@ -1,10 +1,5 @@
 <?php
 
-require_once(DOCROOT . 'inc/wfpl/session.php');
-
 function admin_main() {
-       if(!logged_in_as_admin()) {
-               $_REQUEST['url'] = this_url();
-               return 'admin_login';
-       }
+       session_auth_must('admin_control_panel');
 }
diff --git a/admin_admins.html b/admin_admins.html
deleted file mode 100644 (file)
index b19cc15..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE html>
-
-<html>
-<head>
-       <meta charset="utf-8" />
-       <title><!--~$title show {~-->Accounts<!--~}~--></title>
-       <link rel="stylesheet" href="style.css" type="text/css">
-</head>
-
-<body>
-<!--~$body show {~-->
-
-       <!--~form {~-->
-               <h2><!--~new_msg {~-->Add a new account<!--~}~--><!--~edit_msg {~-->Edit account "~username html~"<!--~}~--></h2>
-
-               <form action="~$basename~" method="post"><!--~editing {~--><div style="display: none"><input type="hidden" name="edit_id" value="~id attr~"></div><!--~}~-->
-
-                       <div class="caption">Name (optional)</div>
-                       <div class="field"><input type="text" name="name" value="~name attr~"></div>
-
-                       <div class="caption">Username (required)</div>
-                       <div class="field_notes">This is used to log in, and is case sensitive, so you may want to stick with all lowercase</div>
-                       <div class="field"><input type="text" name="username" value="~username attr~"></div>
-
-                       <div class="caption">Password (case sensitive)</div>
-                       <div class="field_notes">If this is blank, the user will be unable to log in.</div>
-                       <!--~editing {~--><div class="field_notes">Below you'll see only the encrypted version of the password. This is the only thing that's stored on the server, so if somebody has forgotten their password, the only thing that can be done about it is setting a new password using this field. If you do not edit this field, their password is unchanged.</div><!--~}~-->
-                       <div class="field"><input type="text" name="password" value="~password attr~"></div>
-
-                       <div class="caption">Role</div>
-                       <div class="field_notes">Set to "None" to disable the account. This is useful if you might want to enable it again with the same password, since (unlike deleting the account) the password is preserved in the database.</div>
-                       <div class="field"><select name="privs"><!--~privs options~--></select></div>
-
-                       <div class="caption">&nbsp;</div>
-                       <div class="field"><input type="submit" name="save" value="Save"></div>
-
-               </form>
-
-               <div class="caption">&nbsp;</div>
-               <div class="field"><!--~id {~--><a href="admin_admins?id=~id~">Cancel</a><!--~}~--><!--~id unset {~--><a href="admin_admins">Cancel</a><!--~}~--></div>
-       <!--~}~-->
-
-       <!--~listings once {~-->
-               <h2>Accounts Listing</h2>
-
-               <!--~listings once_if {~-->
-                       <p><a href="admin_admins?new=1">[Add a new account]</a></p>
-
-                       <table cellspacing="0" cellpadding="4" border="0" summary="" class="evenodd">
-                               <tr><th>Name</th><th>Username</th><th>Role</th><th>&nbsp;</th></tr><!--~listings {~-->
-                               <tr>
-                                       <td class="listing"><a href="admin_admins?edit_id=~id~">~name html~<!--~name empty {~--><em>(blank)</em><!--~}~--></a></td>
-                                       <td class="listing"><a href="admin_admins?edit_id=~id~">~username html~<!--~username empty {~--><em>(blank)</em><!--~}~--></a></td>
-                                       <td class="listing"><a href="admin_admins?edit_id=~id~">~privs html~<!--~privs empty {~--><em>(blank)</em><!--~}~--></a></td>
-                                       <td><a href="admin_admins?admin_admins_delete_id=~id~" onclick="return confirm('Permanently delete?')">[delete this account]</a></td>
-                               </tr><!--~}~-->
-
-                       </table>
-               <!--~}~-->
-               <!--~listings once_else {~-->
-                       <p>No accounts in database.</p>
-               <!--~}~-->
-
-               <p><a href="admin_admins?new=1">[Add a new account]</a></p>
-       <!--~}~-->
-
-<!--~}~-->
-</body>
-</html>
diff --git a/admin_admins.php b/admin_admins.php
deleted file mode 100644 (file)
index 6a8f0c4..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-
-# Reset password from the commandline: echo -E "update admins set password="$(echo '<?php print(sha1("NEW_PASSWORD"));' | php)" where username='USERNAME';" | mysql DB_NAME_HERE
-
-define('ADMIN_ADMINS_DB_FIELDS', 'name,username,password,privs');
-
-
-require_once(DOCROOT . 'inc/wfpl/format.php');
-require_once(DOCROOT . 'inc/wfpl/email.php');
-
-function admin_admins_get_fields() {
-       $data = array();
-
-       $data['name'] = format_oneline(_REQUEST_cut('name'));
-       $data['username'] = format_oneline(_REQUEST_cut('username'));
-       $data['password'] = format_oneline(_REQUEST_cut('password'));
-       if($data['password'] && strlen($data['password']) != 40) {
-               $data['password'] = sha1($data['password']);
-       }
-       $data['privs'] = format_options(_REQUEST_cut('privs'), 'privs');
-
-       return $data;
-}
-
-
-function admin_admins_main() {
-       if(logged_in_as_admin()) {
-               tem_set('admin_privs');
-       } else {
-               $_REQUEST['url'] = this_url();
-               return 'admin_login';
-       }
-
-       $id = _REQUEST_cut('edit_id');
-       if($id) {
-               return admin_admins_main_form($id);
-       }
-
-       $id = _REQUEST_cut('admin_admins_delete_id');
-       if($id) {
-               return admin_admins_main_delete($id);
-       }
-
-       if(_REQUEST_cut('new')) {
-               return admin_admins_main_form();
-       }
-
-       if(_REQUEST_cut('list')) {
-               return admin_admins_main_listing();
-       }
-
-       if(isset($_POST['username'])) {
-               return admin_admins_main_form();
-       }
-
-       # default action:
-       return admin_admins_main_listing();
-}
-
-function admin_admins_main_delete($id) {
-       db_delete('admins', 'where id=%i', $id);
-       message('Account deleted.');
-       return './admin_admins';
-}
-
-function admin_admins_main_listing() {
-       $listing_rows = db_get_assocs('admins', 'id,name,username,privs', 'order by coalesce(nullif("",name),username)');
-       tem_set('listings', $listing_rows);
-}
-
-function admin_admins_main_form($id = false) {
-       pulldown('privs', array(
-               array('', 'None'),
-               array('admin', 'Admin')
-       ));
-
-       if($id) {
-               # add hidden field for database id of row we're editing
-               tem_set('id', $id);
-               tem_set('editing');
-               tem_set('edit_msg');
-       } else {
-               tem_set('new_msg');
-       }
-
-       if(isset($_POST['username'])) {
-               $data = admin_admins_get_fields();
-
-               if($data['username']) {
-                       if($id) {
-                               db_update_assoc('admins', $data, 'where id=%i', $id);
-                               message('Account updated.');
-                       } else {
-                               db_insert_assoc('admins', $data);
-                               message('Account saved.');
-                       }
-                       if($error !== true) {
-                               return './admin_admins';
-                       }
-               } else {
-                       message('"username" is required. To disable an account without deleting it, make the password blank');
-               }
-       } elseif($id) {
-               # we've recieved an edit id, but no data. So we grab the values to be edited from the database
-               $data = db_get_assoc('admins', ADMIN_ADMINS_DB_FIELDS, 'where id=%i', $id);
-       } else {
-               # form not submitted, you can set default values:
-               $data = array(
-                       'password' => session_generate_key() # [a-zA-Z0-9]{16}
-               );
-       }
-
-       tem_set('form', $data);
-}
diff --git a/admin_admins.sql b/admin_admins.sql
deleted file mode 100644 (file)
index b276d69..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-drop table if exists admins;
-create table admins (
-    id int unique auto_increment,
-    name varchar(100) not null default "",
-    username varchar(50) not null default "",
-    password varchar(50) not null default "",
-    privs varchar(100) not null default ""
-);
-insert into admins (username,password,privs) values (
-       'fixme',
-       '98fd71615b073b75810f4ed40d4538198c6450cc', /* sha1("fixme") */
-       'admin');
index 98c98e3..1531d9f 100644 (file)
@@ -44,12 +44,7 @@ function admin_files_get_fields() {
 
 
 function admin_files_main() {
-       if(logged_in_as_admin()) {
-               tem_set('admin_privs');
-       } else {
-               $_REQUEST['url'] = this_url();
-               return 'admin_login';
-       }
+       session_auth_must('manage_files');
 
        $id = _REQUEST_cut('edit_id');
        if($id) {
index 9930889..05ff2ce 100644 (file)
@@ -55,10 +55,7 @@ function admin_images_get_fields() {
 
 
 function admin_images_main() {
-       if(!logged_in_as_admin()) {
-               $_REQUEST['url'] = this_url();
-               return 'admin_login';
-       }
+       session_auth_must('admin_images');
 
        $id = _REQUEST_cut('edit_id');
        if($id) {
diff --git a/admin_login.html b/admin_login.html
deleted file mode 100644 (file)
index 4dc10b7..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-
-<html>
-<head>
-       <title><!--~$title show {~-->~$host~ Admin Login<!--~}~--></title>
-</head>
-
-<body>
-       <!--~$body show {~-->
-               <!--~form {~-->
-                       <h2>~$host~ Admin Login</h2>
-
-                       <form action="admin_login" method="post">
-                               <div class="caption">Username (case sensitive)</div>
-                               <div class="field"><input type="text" name="username" value="~username attr~" autofocus></div>
-
-                               <div class="caption">Password (case sensitive)</div>
-                               <div class="field"><input type="password" name="password" value=""></div>
-
-                               <div class="caption">&nbsp;</div>
-                               <div class="field"><input type="hidden" name="url" value="~url attr~"><input type="submit" value="Log in"></div>
-                       </form>
-               <!--~}~-->
-       <!--~}~-->
-</body>
-</html>
diff --git a/admin_login.php b/admin_login.php
deleted file mode 100644 (file)
index e68f86e..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-
-# This form requires wfpl. See: http://sametwice.com/wfpl
-
-function admin_login_get_fields() {
-       $data = array();
-
-       $data['url'] = format_oneline($_REQUEST['url']);
-       $data['username'] = format_oneline($_REQUEST['username']);
-       $data['password'] = sha1(format_oneline($_REQUEST['password']));
-
-       return $data;
-}
-
-
-function admin_login_main() {
-       # Always accept "url" parameter, so might as well just:
-       $data = admin_login_get_fields();
-
-       if(strlen($data['username'])) {
-               $row = db_get_assoc('admins', 'privs', 'where username=%" && password=%"', $data['username'], $data['password']);
-               if($row) {
-                       session_new();
-                       session_set('auth_username', $data['username']);
-                       session_set('auth_' . $row['privs'], 'yes');
-                       if(!$data['url']) {
-                               if ($row['privs'] == 'admin') {
-                                       $data['url'] = './admin';
-                               } else {
-                                       $data['url'] = './';
-                               }
-                       } elseif(strpos(':', $data['url']) !== false) {
-                               $data['url'] = "./$data[url]";
-                       }
-
-                       # redirect to the page they were trying to access:
-                       return $data['url'];
-               } else {
-                       message('Incorrect username and/or password.');
-               }
-       }
-
-       # make sure the hashed password doesn't make it back to the front end
-       $data['password'] = '';
-
-       # display the form [again]
-       tem_set('form', $data);
-}
index 7769d73..3faba82 100644 (file)
@@ -31,10 +31,7 @@ function admin_pages_get_fields() {
 
 
 function admin_pages_main() {
-       if(!logged_in_as_admin()) {
-               $_REQUEST['url'] = this_url();
-               return 'admin_login';
-       }
+       session_auth_must('edit_page');
 
        $id = _REQUEST_cut('edit_id');
        if($id) {
index 5be892e..a1506ec 100644 (file)
@@ -13,6 +13,7 @@ date_default_timezone_set('America/New_York');
 require_once(DOCROOT . 'inc/wfpl/format.php');
 require_once(DOCROOT . 'inc/wfpl/db.php');
 require_once(DOCROOT . 'inc/wfpl/session_messages.php');
+require_once(DOCROOT . 'inc/session_auth.php');
 require_once(DOCROOT . 'inc/cms.php');
 
 # Connect to the database
index 0116c37..6628dea 100644 (file)
@@ -26,7 +26,7 @@ function cms_display($basename, &$tem) {
 
        $cms_page_id = cms_display_content($tem, 'where filename=%"', $basename);
 
-       if(logged_in_as_admin()) {
+       if(session_auth_can('admin_links')) {
                $admin_links = array();
                if($cms_page_id) {
                        $admin_links['id'] = $cms_page_id;
diff --git a/inc/session_auth.php b/inc/session_auth.php
new file mode 100644 (file)
index 0000000..9e49d37
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+# normalize usernames (for case-insensitive etc. logins)
+function format_auth_username($str) {
+       $str = iconv('utf8', 'ascii//TRANSLIT', $str);
+       $str = strtolower(trim($str));
+       $str = preg_replace('/[^a-z0-9]/', '', $str);
+       return $str;
+}
+
+# Called automatically by session_auth().
+# Only call if you've just verified that someone has logged in, or has clicked
+# a valid password reset link.
+function session_auth_init($id = false, $password_reset = false) {
+       $GLOBALS['wfpl_session_auth'] = [
+               'id' => null,
+               'role' => null,
+               'name' => null,
+               'username' => null,
+               'last_active' => null,
+               'password_reset' => null
+       ];
+
+       if ($id) {
+               $user = db_get_assoc('users', 'role,name,username', 'where id=%i', $id);
+               $now = time();
+               db_update('users', 'last_active', $now, 'where id=%i', $id);
+               $GLOBALS['wfpl_session_auth']['id'] = $id;
+               $GLOBALS['wfpl_session_auth']['role'] = $user['role'];
+               $GLOBALS['wfpl_session_auth']['name'] = $user['name'];
+               $GLOBALS['wfpl_session_auth']['username'] = $user['username'];
+               $GLOBALS['wfpl_session_auth']['last_active'] = $now;
+       }
+
+       if ($password_reset) {
+               $GLOBALS['wfpl_session_auth']['password_reset'] = true;
+               $GLOBALS['wfpl_session_auth']['id'] = session_get('auth_password_reset_id');
+       }
+}
+
+# return an assoc containing info about the authenticated user, see session_auth_init
+function session_auth() {
+       if (!isset($GLOBALS['wfpl_session_auth'])) {
+               $id = false;
+               $reset = false;
+               if (session_exists()) {
+                       $id = session_get('auth_id');
+                       if (!$id) {
+                               $r = session_get('auth_password_reset');
+                               if (strlen($r)) {
+                                       $r = (int) format_int_0($r);
+                                       if (time() < $r) {
+                                               $reset = true;
+                                       } else {
+                                               message('Oops, your temporary access (to change your password) has expired');
+                                               session_clear('auth_password_reset');
+                                       }
+                               }
+                       }
+               }
+               session_auth_init($id, $reset);
+       }
+       return $GLOBALS['wfpl_session_auth'];
+}
+
+# return true if the logged in user is allowed to $priv
+# (false if they are not logged in, or aren't alowed to $priv)
+function session_auth_can($priv) {
+       $s = session_auth();
+       if ($s['role'] === 'admin') {
+               return true;
+       }
+       return false;
+}
+
+# return ONLY IF the currently logged in user can $priv
+# otherwise, it displays the login page, and exit early
+function session_auth_must($priv) {
+       if (session_auth_can($priv)) {
+               return;
+       }
+       if (!isset($_REQUEST['after_login'])) {
+               $_REQUEST['after_login_url'] = this_url();
+       }
+       wfpl_main('login');
+       exit();
+}
diff --git a/login.html b/login.html
new file mode 100644 (file)
index 0000000..6a3fbcf
--- /dev/null
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+       <title></title>
+</head>
+
+<body>
+       <!--~$body show {~-->
+               <!--~form {~-->
+                       <form action="login" method="post">
+                               <div class="caption">Username</div>
+                               <div class="field"><input type="text" name="username" value="~username attr~" autofocus></div>
+
+                               <div class="caption">Password (case sensitive)</div>
+                               <div class="field"><input type="password" name="password" value=""></div>
+
+                               <div class="caption">&nbsp;</div>
+                               <div class="field"><input type="hidden" name="after_login_url" value="~after_login_url attr~"><input type="submit" value="Log in"></div>
+                       </form>
+               <!--~}~-->
+       <!--~}~-->
+</body>
+</html>
diff --git a/login.php b/login.php
new file mode 100644 (file)
index 0000000..4ec1344
--- /dev/null
+++ b/login.php
@@ -0,0 +1,67 @@
+<?php
+
+
+function login_get_fields() {
+       $data = array();
+
+       $data['after_login_url'] = format_oneline(_REQUEST_cut('after_login_url'));
+       $data['username'] = format_oneline(trim(_REQUEST_cut('username')));
+       $data['password'] = format_oneline(trim(_REQUEST_cut('password')));
+
+       return $data;
+}
+
+function login_main() {
+       $data = login_get_fields();
+       if (strlen($data['username']) && strlen($data['password'])) {
+               $row = db_get_assoc('users', 'id,name,role,password', 'where username=%"', format_auth_username($data['username']));
+               if ($row) # &&
+               if (strlen($row['password'])) {
+                       $needs_rehash = false;
+                       $password_good = false;
+                       if (substr($row['password'], 0, 5) === 'sha1:') {
+                               if (sha1($data['password']) === substr($row['password'], 5)) {
+                                       $password_good = true;
+                                       $needs_rehash = true;
+                               }
+                       } else {
+                               if (!function_exists('password_hash')) {
+                                       require_once(DOCROOT . 'inc/password_funcs_backported.php');
+                               }
+                               if (password_verify($data['password'], $row['password'])) {
+                                       $password_good = true;
+                                       if (password_needs_rehash($row['password'], PASSWORD_DEFAULT)) {
+                                               $needs_rehash = true;
+                                       }
+                               }
+                       }
+                       if ($password_good) {
+                               if ($needs_rehash) {
+                                       $hash = password_hash($data['password'], PASSWORD_DEFAULT);
+                                       db_update('users', 'password', $hash, 'where id=%i', $row['id']);
+                               }
+
+                               session_new();
+                               session_set('auth_id', $row['id']);
+                               # we're about to http redirect, so no need to update session_auth now
+                               db_update('users', 'last_login', time(), 'where id=%i', $row['id']);
+                               message("You are now logged in.");
+                               if(!$data['after_login_url']) {
+                                       if ($row['role'] == 'admin') {
+                                               $data['after_login_url'] = './admin';
+                                       } else {
+                                               $data['after_login_url'] = './';
+                                       }
+                               } elseif(strpos(':', $data['after_login_url']) !== false) {
+                                       $data['after_login_url'] = "./$data[url]";
+                               }
+
+                               # redirect to the page they were trying to access:
+                               return $data['after_login_url'];
+                       }
+               }
+               message("Incorrect username and/or password");
+       }
+       $data['password'] = '';
+       tem_set('form', $data);
+}