From e89d30bb98dd851b93fa14fcba4c5865c08819ad Mon Sep 17 00:00:00 2001 From: Bartek Fabiszewski Date: Fri, 14 Apr 2017 15:55:00 +0200 Subject: [PATCH] Optionally enforce password strength (adds config options) --- config.default.php | 10 ++++++++++ helpers/config.php | 49 +++++++++++++++++++++++++++++++++++++--------- helpers/user.php | 38 ++++++++++++++++++++++++++--------- index.php | 1 + js/admin.js | 4 ++++ js/pass.js | 5 +++++ lang.php | 5 ++++- lang/en.php | 4 ++++ 8 files changed, 97 insertions(+), 19 deletions(-) diff --git a/config.default.php b/config.default.php index 3232cdf..262fcb9 100755 --- a/config.default.php +++ b/config.default.php @@ -62,6 +62,16 @@ $public_tracks = 0; // none if empty $admin_user = "admin"; +// miniumum required length of user password +$pass_lenmin = 12; + +// required strength of user password +// 0 = no requirements, +// 1 = require mixed case letters (lower and upper), +// 2 = require mixed case and numbers +// 3 = require mixed case, numbers and non-alphanumeric characters +$pass_strength = 2; + // Default interval in seconds for live auto reload $interval = 10; diff --git a/helpers/config.php b/helpers/config.php index f47ed84..8f2e4bd 100644 --- a/helpers/config.php +++ b/helpers/config.php @@ -25,7 +25,6 @@ static $version = "0.2-beta"; // default map drawing framework - // (gmaps = google maps, openlayers = openlayers/osm) static $mapapi = "openlayers"; // gmaps key @@ -45,17 +44,12 @@ static $init_latitude = 52.23; static $init_longitude = 21.01; - // you may set your google maps api key - // this is not obligatory by now - //$gkey = ""; - // MySQL config static $dbhost = ""; // mysql host, eg. localhost static $dbuser = ""; // database user static $dbpass = ""; // database pass static $dbname = ""; // database name - // other // require login/password authentication static $require_authentication = true; @@ -66,15 +60,23 @@ // none if empty static $admin_user = ""; + // miniumum required length of user password + static $pass_lenmin = 12; + + // required strength of user password + // 0 = no requirements, + // 1 = require mixed case letters (lower and upper), + // 2 = require mixed case and numbers + // 3 = require mixed case, numbers and non-alphanumeric characters + static $pass_strength = 2; + // Default interval in seconds for live auto reload static $interval = 10; // Default language - // (en, pl, de, hu, fr, it) static $lang = "en"; // units - // (metric, imperial) static $units = "metric"; private static $fileLoaded = false; @@ -113,7 +115,9 @@ if (isset($require_authentication)) { self::$require_authentication = (bool) $require_authentication; } if (isset($public_tracks)) { self::$public_tracks = (bool) $public_tracks; } if (isset($admin_user)) { self::$admin_user = $admin_user; } - if (isset($interval)) { self::$interval = $interval; } + if (isset($pass_lenmin)) { self::$pass_lenmin = (int) $pass_lenmin; } + if (isset($pass_strength)) { self::$pass_strength = (int) $pass_strength; } + if (isset($interval)) { self::$interval = (int) $interval; } if (isset($lang)) { self::$lang = $lang; } if (isset($units)) { self::$units = $units; } @@ -132,6 +136,33 @@ if (isset($_COOKIE["ulogger_units"])) { self::$units = $_COOKIE["ulogger_units"]; } if (isset($_COOKIE["ulogger_interval"])) { self::$interval = $_COOKIE["ulogger_interval"]; } } + + /** + * Regex to test if password matches strength and length requirements. + * Valid for both php and javascript + */ + public function passRegex() { + static $regex = ""; + if (self::$pass_strength > 0) { + // lower and upper case + $regex .= "(?=.*[a-z])(?=.*[A-Z])"; + } + if (self::$pass_strength > 1) { + // digits + $regex .= "(?=.*[0-9])"; + } + if (self::$pass_strength > 2) { + // not latin, not digits + $regex .= "(?=.*[^a-zA-Z0-9])"; + } + if (self::$pass_lenmin > 0) { + $regex .= "(?=.{" . self::$pass_lenmin . ",})"; + } + if (!empty($regex)) { + $regex = "/" . $regex . "/"; + } + return $regex; + } } ?> \ No newline at end of file diff --git a/helpers/user.php b/helpers/user.php index d03a0c9..41ddf54 100644 --- a/helpers/user.php +++ b/helpers/user.php @@ -65,7 +65,7 @@ */ public function add($login, $pass) { $userid = false; - if (!empty($login) && !empty($pass)) { + if (!empty($login) && !empty($pass) && $this->validPassStrength($pass)) { $hash = password_hash($pass, PASSWORD_DEFAULT); $sql = "INSERT INTO users (login, password) VALUES (?, ?)"; $stmt = self::$db->prepare($sql); @@ -105,6 +105,11 @@ $stmt->execute(); if (!self::$db->error && !$stmt->errno) { $ret = true; + $this->id = NULL; + $this->login = NULL; + $this->hash = NULL; + $this->isValid = false; + $this->isAdmin = false; } $stmt->close(); } @@ -118,16 +123,18 @@ * @return bool True on success, false otherwise */ public function setPass($pass) { - $hash = password_hash($pass, PASSWORD_DEFAULT); $ret = false; - $sql = "UPDATE users SET password = ? WHERE login = ?"; - $stmt = self::$db->prepare($sql); - $stmt->bind_param('ss', $hash, $this->login); - $stmt->execute(); - if (!self::$db->error && !$stmt->errno) { - $ret = true; + if ($this->validPassStrength($pass)) { + $hash = password_hash($pass, PASSWORD_DEFAULT); + $sql = "UPDATE users SET password = ? WHERE login = ?"; + $stmt = self::$db->prepare($sql); + $stmt->bind_param('ss', $hash, $this->login); + $stmt->execute(); + if (!self::$db->error && !$stmt->errno) { + $ret = true; + } + $stmt->close(); } - $stmt->close(); return $ret; } @@ -141,6 +148,17 @@ return password_verify($password, $this->hash); } + /** + * Check if given password matches user's one + * + * @param String $password Password + * @return bool True if matches, false otherwise + */ + private function validPassStrength($password) { + $config = new uConfig(); + return preg_match($config->passRegex(), $password); + } + /** * Store uUser object in session */ @@ -150,6 +168,7 @@ /** * Fill uUser object properties from session data + * @return uPosition Self */ public function getFromSession() { if (isset($_SESSION['user'])) { @@ -160,6 +179,7 @@ $this->isAdmin = $sessionUser->isAdmin; $this->isValid = $sessionUser->isValid; } + return $this; } /** diff --git a/index.php b/index.php index 6cfc78c..9855869 100755 --- a/index.php +++ b/index.php @@ -81,6 +81,7 @@ var init_longitude = ''; var lang = ; var auth = 'isValid) ? $user->login : "null" ?>'; + var pass_regex = passRegex() ?>; diff --git a/js/admin.js b/js/admin.js index 0153ec0..8fcca1a 100644 --- a/js/admin.js +++ b/js/admin.js @@ -70,6 +70,10 @@ function submitUser(action) { alert(lang['passnotmatch']); return; } + if (!pass_regex.test(pass)) { + alert(lang['passlenmin'] + '\n' + lang['passrules']); + return; + } } else { if (!confirmedDelete(login)) { return; diff --git a/js/pass.js b/js/pass.js index 7e59688..9c7b284 100644 --- a/js/pass.js +++ b/js/pass.js @@ -39,6 +39,11 @@ function submitPass() { alert(lang['passnotmatch']); return; } + if (!pass_regex.test(pass)) { + alert(lang['passlenmin'] + '\n' + lang['passrules']); + return; + } + var xhr = getXHR(); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { diff --git a/lang.php b/lang.php index 1a16ca3..b3dff4b 100755 --- a/lang.php +++ b/lang.php @@ -17,7 +17,6 @@ * along with this program; if not, see . */ - // available languages $langsArr = [ "en" => "English", @@ -38,4 +37,8 @@ require_once(ROOT_DIR . "/lang/{$config::$lang}.php"); } + // choose password messages based on config + $lang['passrules'] = $lang["passrules"][$config::$pass_strength]; + $lang['passlenmin'] = sprintf($lang["passlenmin"], $config::$pass_lenmin); + ?> diff --git a/lang/en.php b/lang/en.php index 1f9e855..4291885 100644 --- a/lang/en.php +++ b/lang/en.php @@ -76,5 +76,9 @@ $lang["deletewarn"] = "Warning!\n\nYou are going to permanently delete user %s, $lang["editinguser"] = "You are editing user %s"; // substitutes user login $lang["selfeditwarn"] = "Your can't edit your own user with this tool"; $lang["apifailure"] = "Sorry, can't load %s API"; // substitures api name (gmaps or openlayers) +$lang["passlenmin"] = "Password must be at least %d characters"; // substitutes password minimum length +$lang["passrules"][1] = "It should contain at least one upper case letter, one lower case letter"; +$lang["passrules"][2] = "It should contain at least one upper case letter, one lower case letter and one digit"; +$lang["passrules"][3] = "It should contain at least one upper case letter, one lower case letter, one digit and one non-alphanumeric character"; ?>