From 128e09cd5db316c5e97e4c137f6d85fa51fb1f0d Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 23 May 2013 11:10:17 +0200 Subject: [PATCH 1/4] initial work on password reset, not working fully yet --- public/include/classes/user.class.php | 61 ++++++++++++++++++--- public/include/pages/password.inc.php | 9 +++ public/include/pages/password/reset.inc.php | 15 +++++ public/templates/mmcFE/global/login.tpl | 2 +- public/templates/mmcFE/password/default.tpl | 8 +++ 5 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 public/include/pages/password.inc.php create mode 100644 public/include/pages/password/reset.inc.php create mode 100644 public/templates/mmcFE/password/default.tpl diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index e33b8c7c..3ec72546 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -11,10 +11,11 @@ class User { private $user = array(); private $tableAccountBalance = 'accountBalance'; - public function __construct($debug, $mysqli, $salt) { + public function __construct($debug, $mysqli, $salt, $config) { $this->debug = $debug; $this->mysqli = $mysqli; $this->salt = $salt; + $this->config = $config; $this->debug->append("Instantiated User class", 2); } @@ -34,6 +35,23 @@ class User { return $this->getSingle($username, 'id', 'username', 's'); } + public function getUserEmail($username) { + return $this->getSingle($username, 'email', 'username', 's'); + } + + public function getUserToken($id) { + return $this->getSingle($id, 'token', 'id'); + } + + public function setUserToken($id) { + $field = array( + 'name' => 'token', + 'type' => 's', + 'value' => hash('sha256', $id.time().$this->salt) + ); + return $this->updateSingle($id, $field); + } + /** * Check user login * @param username string Username @@ -142,15 +160,12 @@ class User { * @param field string Field to update * @return bool **/ - private function updateSingle($userID, $field) { + private function updateSingle($id, $field) { $this->debug->append("STA " . __METHOD__, 4); - $stmt = $this->mysqli->prepare("UPDATE $this->table SET " . $field['name'] . " = ? WHERE userId = ? LIMIT 1"); - if ($this->checkStmt($stmt)) { - $stmt->bind_param($field['type'].'i', $field['value'], $userID); - $stmt->execute(); - $stmt->close(); + $stmt = $this->mysqli->prepare("UPDATE $this->table SET " . $field['name'] . " = ? WHERE id = ? LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param($field['type'].'i', $field['value'], $id) && $stmt->execute()) return true; - } + $this->debug->append("Unable to update " . $field['name'] . " with " . $field['value'] . " for ID $id"); return false; } @@ -306,6 +321,34 @@ class User { } return false; } + + public function resetPassword($username) { + $this->debug->append("STA " . __METHOD__, 4); + // Fetch the users mail address + if (!$email = $this->getUserEmail($username)) { + $this->setErrorMessage("Unable to find a mail address for user $username"); + return false; + } + if (!$this->setUserToken($this->getUserId($username))) { + $this->setErrorMessage("Unable to setup token for password reset"); + return false; + } + // Send password reset link + if (!$token = $this->getUserToken($this->getUserId($username))) { + $this->setErrorMessage("Unable fetch token for password reset"); + return false; + } + $subject = "[" . $this->config['website']['name'] . "] Password Reset Request"; + $header = "From: " . $this->config['website']['email']; + $message = "Please follow the link to reset your password\n\n" . $this->config['website']['url']['password_reset'] . "/index.php?page=password&action=change&token=$token"; + if (mail($email, 'Password Reset Request', $message)) { + return true; + } else { + $this->setErrorMessage("Unable to send mail to your address"); + return false; + } + return false; + } } -$user = new User($debug, $mysqli, SALT); +$user = new User($debug, $mysqli, SALT, $config); diff --git a/public/include/pages/password.inc.php b/public/include/pages/password.inc.php new file mode 100644 index 00000000..aecab054 --- /dev/null +++ b/public/include/pages/password.inc.php @@ -0,0 +1,9 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/password/reset.inc.php b/public/include/pages/password/reset.inc.php new file mode 100644 index 00000000..532eb67c --- /dev/null +++ b/public/include/pages/password/reset.inc.php @@ -0,0 +1,15 @@ +resetPassword($_POST['username'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mail account to finish your password reset'); +} else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); +} + +// Tempalte specifics, user default template by parent page +$smarty->assign("CONTENT", "../default.tpl"); +?> diff --git a/public/templates/mmcFE/global/login.tpl b/public/templates/mmcFE/global/login.tpl index 0247c665..d411d39a 100644 --- a/public/templates/mmcFE/global/login.tpl +++ b/public/templates/mmcFE/global/login.tpl @@ -4,5 +4,5 @@

-

Forgot your password?

+

Forgot your password?

{include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/password/default.tpl b/public/templates/mmcFE/password/default.tpl new file mode 100644 index 00000000..35b4a0eb --- /dev/null +++ b/public/templates/mmcFE/password/default.tpl @@ -0,0 +1,8 @@ +{include file="global/block_header.tpl" BLOCK_HEADER="Reset Password" BLOCK_STYLE="clear:none;"} +
+ + +

If you have an email set for your account, enter your username to get your password reset

+

+
+{include file="global/block_footer.tpl"} From 841d9867269004216025f16c74bd8ea4930f7975 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 23 May 2013 11:10:17 +0200 Subject: [PATCH 2/4] initial work on password reset, not working fully yet --- public/include/classes/user.class.php | 61 ++++++++++++++++++--- public/include/pages/password.inc.php | 9 +++ public/include/pages/password/reset.inc.php | 15 +++++ public/templates/mmcFE/global/login.tpl | 2 +- public/templates/mmcFE/password/default.tpl | 8 +++ 5 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 public/include/pages/password.inc.php create mode 100644 public/include/pages/password/reset.inc.php create mode 100644 public/templates/mmcFE/password/default.tpl diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index e33b8c7c..3ec72546 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -11,10 +11,11 @@ class User { private $user = array(); private $tableAccountBalance = 'accountBalance'; - public function __construct($debug, $mysqli, $salt) { + public function __construct($debug, $mysqli, $salt, $config) { $this->debug = $debug; $this->mysqli = $mysqli; $this->salt = $salt; + $this->config = $config; $this->debug->append("Instantiated User class", 2); } @@ -34,6 +35,23 @@ class User { return $this->getSingle($username, 'id', 'username', 's'); } + public function getUserEmail($username) { + return $this->getSingle($username, 'email', 'username', 's'); + } + + public function getUserToken($id) { + return $this->getSingle($id, 'token', 'id'); + } + + public function setUserToken($id) { + $field = array( + 'name' => 'token', + 'type' => 's', + 'value' => hash('sha256', $id.time().$this->salt) + ); + return $this->updateSingle($id, $field); + } + /** * Check user login * @param username string Username @@ -142,15 +160,12 @@ class User { * @param field string Field to update * @return bool **/ - private function updateSingle($userID, $field) { + private function updateSingle($id, $field) { $this->debug->append("STA " . __METHOD__, 4); - $stmt = $this->mysqli->prepare("UPDATE $this->table SET " . $field['name'] . " = ? WHERE userId = ? LIMIT 1"); - if ($this->checkStmt($stmt)) { - $stmt->bind_param($field['type'].'i', $field['value'], $userID); - $stmt->execute(); - $stmt->close(); + $stmt = $this->mysqli->prepare("UPDATE $this->table SET " . $field['name'] . " = ? WHERE id = ? LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param($field['type'].'i', $field['value'], $id) && $stmt->execute()) return true; - } + $this->debug->append("Unable to update " . $field['name'] . " with " . $field['value'] . " for ID $id"); return false; } @@ -306,6 +321,34 @@ class User { } return false; } + + public function resetPassword($username) { + $this->debug->append("STA " . __METHOD__, 4); + // Fetch the users mail address + if (!$email = $this->getUserEmail($username)) { + $this->setErrorMessage("Unable to find a mail address for user $username"); + return false; + } + if (!$this->setUserToken($this->getUserId($username))) { + $this->setErrorMessage("Unable to setup token for password reset"); + return false; + } + // Send password reset link + if (!$token = $this->getUserToken($this->getUserId($username))) { + $this->setErrorMessage("Unable fetch token for password reset"); + return false; + } + $subject = "[" . $this->config['website']['name'] . "] Password Reset Request"; + $header = "From: " . $this->config['website']['email']; + $message = "Please follow the link to reset your password\n\n" . $this->config['website']['url']['password_reset'] . "/index.php?page=password&action=change&token=$token"; + if (mail($email, 'Password Reset Request', $message)) { + return true; + } else { + $this->setErrorMessage("Unable to send mail to your address"); + return false; + } + return false; + } } -$user = new User($debug, $mysqli, SALT); +$user = new User($debug, $mysqli, SALT, $config); diff --git a/public/include/pages/password.inc.php b/public/include/pages/password.inc.php new file mode 100644 index 00000000..aecab054 --- /dev/null +++ b/public/include/pages/password.inc.php @@ -0,0 +1,9 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/password/reset.inc.php b/public/include/pages/password/reset.inc.php new file mode 100644 index 00000000..532eb67c --- /dev/null +++ b/public/include/pages/password/reset.inc.php @@ -0,0 +1,15 @@ +resetPassword($_POST['username'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mail account to finish your password reset'); +} else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); +} + +// Tempalte specifics, user default template by parent page +$smarty->assign("CONTENT", "../default.tpl"); +?> diff --git a/public/templates/mmcFE/global/login.tpl b/public/templates/mmcFE/global/login.tpl index 0247c665..d411d39a 100644 --- a/public/templates/mmcFE/global/login.tpl +++ b/public/templates/mmcFE/global/login.tpl @@ -4,5 +4,5 @@

-

Forgot your password?

+

Forgot your password?

{include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/password/default.tpl b/public/templates/mmcFE/password/default.tpl new file mode 100644 index 00000000..35b4a0eb --- /dev/null +++ b/public/templates/mmcFE/password/default.tpl @@ -0,0 +1,8 @@ +{include file="global/block_header.tpl" BLOCK_HEADER="Reset Password" BLOCK_STYLE="clear:none;"} +
+ + +

If you have an email set for your account, enter your username to get your password reset

+

+
+{include file="global/block_footer.tpl"} From 787942b6f90ebab0eef8e4e2f7b1a73d4fe2a397 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Sat, 25 May 2013 12:08:51 +0200 Subject: [PATCH 3/4] working version of password reset with one time token --- public/include/classes/user.class.php | 37 ++++++++++++++++--- public/include/config/global.inc.dist.php | 1 + public/include/pages/password/change.inc.php | 17 +++++++++ public/include/pages/password/reset.inc.php | 3 +- public/templates/mail/body.tpl | 13 +++++++ public/templates/mail/subject.tpl | 1 + .../mmcFE/password/change/default.tpl | 12 ++++++ 7 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 public/include/pages/password/change.inc.php create mode 100644 public/templates/mail/body.tpl create mode 100644 public/templates/mail/subject.tpl create mode 100644 public/templates/mmcFE/password/change/default.tpl diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 3ec72546..3e373ab7 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -43,6 +43,10 @@ class User { return $this->getSingle($id, 'token', 'id'); } + public function getIdFromToken($token) { + return $this->getSingle($token, 'id', 'token', 's'); + } + public function setUserToken($id) { $field = array( 'name' => 'token', @@ -322,7 +326,30 @@ class User { return false; } - public function resetPassword($username) { + public function useToken($token, $new1, $new2) { + $this->debug->append("STA " . __METHOD__, 4); + if ($id = $this->getIdFromToken($token)) { + if ($new1 !== $new2) { + $this->setErrorMessage( 'New passwords do not match' ); + return false; + } + if ( strlen($new1) < 8 ) { + $this->setErrorMessage( 'New password is too short, please use more than 8 chars' ); + return false; + } + $new = hash('sha256', $new1.$this->salt); + $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ?, token = NULL WHERE id = ? AND token = ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param('sis', $new, $id, $token) && $stmt->execute() && $stmt->affected_rows === 1) { + return true; + } + } else { + $this->setErrorMessage("Unable find user for your token"); + return false; + } + return false; + } + + public function resetPassword($username, $smarty) { $this->debug->append("STA " . __METHOD__, 4); // Fetch the users mail address if (!$email = $this->getUserEmail($username)) { @@ -338,10 +365,10 @@ class User { $this->setErrorMessage("Unable fetch token for password reset"); return false; } - $subject = "[" . $this->config['website']['name'] . "] Password Reset Request"; - $header = "From: " . $this->config['website']['email']; - $message = "Please follow the link to reset your password\n\n" . $this->config['website']['url']['password_reset'] . "/index.php?page=password&action=change&token=$token"; - if (mail($email, 'Password Reset Request', $message)) { + $smarty->assign('TOKEN', $token); + $smarty->assign('USERNAME', $username); + $smarty->assign('WEBSITENAME', $this->config['website']['name']); + if (mail($email, $smarty->fetch('templates/mail/subject.tpl'), $smarty->fetch('templates/mail/body.tpl'))) { return true; } else { $this->setErrorMessage("Unable to send mail to your address"); diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 37d39c3f..f684c2bb 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -24,6 +24,7 @@ $config = array( 'website' => array( 'name' => 'The Pool', 'slogan' => 'Resistance is futile', + 'email' => 'test@example.com', // Mail address used for notifications ), 'fees' => 0, 'difficulty' => '31', // Target difficulty for this pool as set in pushpoold json diff --git a/public/include/pages/password/change.inc.php b/public/include/pages/password/change.inc.php new file mode 100644 index 00000000..8b5f4064 --- /dev/null +++ b/public/include/pages/password/change.inc.php @@ -0,0 +1,17 @@ +useToken($_POST['token'], $_POST['newPassword'], $_POST['newPassword2'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Password reset complete! Please login.'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); + } +} + +// Tempalte specifics +$smarty->assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/password/reset.inc.php b/public/include/pages/password/reset.inc.php index 532eb67c..796f5811 100644 --- a/public/include/pages/password/reset.inc.php +++ b/public/include/pages/password/reset.inc.php @@ -4,7 +4,8 @@ if (!defined('SECURITY')) die('Hacking attempt'); -if ($user->resetPassword($_POST['username'])) { +// Process password reset request +if ($user->resetPassword($_POST['username'], $smarty)) { $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mail account to finish your password reset'); } else { $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); diff --git a/public/templates/mail/body.tpl b/public/templates/mail/body.tpl new file mode 100644 index 00000000..ad36501d --- /dev/null +++ b/public/templates/mail/body.tpl @@ -0,0 +1,13 @@ +Hello {$USERNAME}, + +You have requested a password reset through our online form. +In order to complete the request please follow this link + +http://{$smarty.server.SERVER_NAME}{$smarty.server.PHP_SELF}?page=password&action=change&token={$TOKEN} + +You will be asked to change your password. You can then use this new +password to login to your account. + + +Cheers, +Website Administration diff --git a/public/templates/mail/subject.tpl b/public/templates/mail/subject.tpl new file mode 100644 index 00000000..665e26f5 --- /dev/null +++ b/public/templates/mail/subject.tpl @@ -0,0 +1 @@ +[ {$WEBSITENAME} ] Password Reset Request diff --git a/public/templates/mmcFE/password/change/default.tpl b/public/templates/mmcFE/password/change/default.tpl new file mode 100644 index 00000000..0d254677 --- /dev/null +++ b/public/templates/mmcFE/password/change/default.tpl @@ -0,0 +1,12 @@ +{include file="global/block_header.tpl" BLOCK_HEADER="Change Password"} +
+ + + + + + + +
New Password:
New Password Repeat:
+
+{include file="global/block_footer.tpl"} From 93d0ec06a66b56bea205c9626300bc4e31ec4614 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Sat, 25 May 2013 12:25:41 +0200 Subject: [PATCH 4/4] adding proper headers for HTML mail --- public/include/classes/user.class.php | 8 +++++++- public/templates/mail/body.tpl | 23 ++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 3e373ab7..d54b2da5 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -368,7 +368,13 @@ class User { $smarty->assign('TOKEN', $token); $smarty->assign('USERNAME', $username); $smarty->assign('WEBSITENAME', $this->config['website']['name']); - if (mail($email, $smarty->fetch('templates/mail/subject.tpl'), $smarty->fetch('templates/mail/body.tpl'))) { + $headers = 'From: Website Administration <' . $this->config['website']['email'] . ">\n"; + $headers .= "MIME-Version: 1.0\n"; + $headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n"; + if (mail($email, + $smarty->fetch('templates/mail/subject.tpl'), + $smarty->fetch('templates/mail/body.tpl'), + $headers)) { return true; } else { $this->setErrorMessage("Unable to send mail to your address"); diff --git a/public/templates/mail/body.tpl b/public/templates/mail/body.tpl index ad36501d..a80b579a 100644 --- a/public/templates/mail/body.tpl +++ b/public/templates/mail/body.tpl @@ -1,13 +1,10 @@ -Hello {$USERNAME}, - -You have requested a password reset through our online form. -In order to complete the request please follow this link - -http://{$smarty.server.SERVER_NAME}{$smarty.server.PHP_SELF}?page=password&action=change&token={$TOKEN} - -You will be asked to change your password. You can then use this new -password to login to your account. - - -Cheers, -Website Administration + + +

Hello {$USERNAME},


+

You have requested a password reset through our online form. In order to complete the request please follow this link:

+

http://{$smarty.server.SERVER_NAME}{$smarty.server.PHP_SELF}?page=password&action=change&token={$TOKEN}

+

You will be asked to change your password. You can then use this new password to login to your account.

+

Cheers,

+

Website Administration

+ +