From ef904858ae5cdc299f692ea35b7c8adb3305f460 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 02:49:58 -0500 Subject: [PATCH 01/23] [Addition] E-mail confirmations for user actions * If enabled, sends e-mail to confirm user withdraws, edits and pw changes * Adds 4 config options, enabled + individual settings * Adds 3 new token_types --- public/include/classes/payout.class.php | 33 ++++++++++- public/include/classes/token.class.php | 17 ++++++ public/include/classes/user.class.php | 58 ++++++++++++++++++- public/include/config/global.inc.dist.php | 24 ++++++++ public/include/pages/account/edit.inc.php | 11 ++-- .../mail/notifications/account_edit.tpl | 9 +++ .../mail/notifications/change_pw.tpl | 9 +++ .../mail/notifications/withdraw_funds.tpl | 9 +++ .../templates/mpos/account/edit/default.tpl | 3 + sql/000_base_structure.sql | 6 +- sql/013_tokentype_update.sql | 3 + 11 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 public/templates/mail/notifications/account_edit.tpl create mode 100644 public/templates/mail/notifications/change_pw.tpl create mode 100644 public/templates/mail/notifications/withdraw_funds.tpl create mode 100644 sql/013_tokentype_update.sql diff --git a/public/include/classes/payout.class.php b/public/include/classes/payout.class.php index d7c2cae1..96cd5141 100644 --- a/public/include/classes/payout.class.php +++ b/public/include/classes/payout.class.php @@ -32,10 +32,36 @@ class Payout Extends Base { /** * Insert a new payout request - * @param account_id Account ID + * @param account_id int Account ID + * @param strToken string Token to confirm * @return data mixed Inserted ID or false **/ - public function createPayout($account_id=NULL) { + public function createPayout($account_id=NULL, $strToken) { + // twofactor - if cashout enabled we need to create/check the token + if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['withdraw']) { + $tData = $this->token->getToken($strToken, 'withdraw_funds'); + $tExists = $this->token->doesTokenExist('withdraw_funds', $account_id); + if (!is_array($tData) && $tExists == false) { + // token doesn't exist, let's create one, send an email with a link to use it, and error out + $token = $this->token->createToken('withdraw_funds', $account_id); + $aData['token'] = $token; + $aData['username'] = $this->getUserName($account_id); + $aData['email'] = $this->getUserEmail($aData['username']); + $aData['subject'] = 'Manual payout request confirmation'; + $this->mail->sendMail('notifications/withdraw_funds', $aData); + $this->setErrorMessage("A confirmation has been sent to your e-mail"); + return false; + } else { + // already exists, if it's valid delete it and allow this edit + if ($strToken === $tData['token']) { + $this->token->deleteToken($tData['token']); + } else { + // token exists for this type, but this is not the right token + $this->setErrorMessage("A confirmation was sent to your e-mail, follow that link to cash out"); + return false; + } + } + } $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id) VALUES (?)"); if ($stmt && $stmt->bind_param('i', $account_id) && $stmt->execute()) { return $stmt->insert_id; @@ -59,6 +85,9 @@ class Payout Extends Base { $oPayout = new Payout(); $oPayout->setDebug($debug); $oPayout->setMysql($mysqli); +$oPayout->setConfig($config); +$oPayout->setMail($mail); +$oPayout->setToken($oToken); $oPayout->setErrorCodes($aErrorCodes); ?> diff --git a/public/include/classes/token.class.php b/public/include/classes/token.class.php index 8453c245..ca68ebc7 100644 --- a/public/include/classes/token.class.php +++ b/public/include/classes/token.class.php @@ -21,6 +21,23 @@ class Token Extends Base { return $result->fetch_assoc(); return $this->sqlError(); } + + /** + * Check if a token of this type already exists for a given account_id + * @param strType string Name of the type of token + * @param account_id int Account id of user to check + * @return mixed Number of rows on success, false on failure + */ + public function doesTokenExist($strType=NULL, $account_id=NULL) { + if (!$iToken_id = $this->tokentype->getTypeId($strType)) { + $this->setErrorMessage('Invalid token type: ' . $strType); + return false; + } + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE account_id = ? AND type = ? LIMIT 1"); + if ($stmt && $stmt->bind_param('ii', $account_id, $iToken_id) && $stmt->execute()) + return $stmt->num_rows; + return $this->sqlError(); + } /** * Insert a new token diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index f335f75f..9f026d59 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -261,9 +261,10 @@ class User extends Base { * @param current string Current password * @param new1 string New password * @param new2 string New password confirmation + * @param strToken string Token for confirmation * @return bool **/ - public function updatePassword($userID, $current, $new1, $new2) { + public function updatePassword($userID, $current, $new1, $new2, $strToken) { $this->debug->append("STA " . __METHOD__, 4); if ($new1 !== $new2) { $this->setErrorMessage( 'New passwords do not match' ); @@ -273,6 +274,31 @@ class User extends Base { $this->setErrorMessage( 'New password is too short, please use more than 8 chars' ); return false; } + // twofactor - if changepw is enabled we need to create/check the token + if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['changepw']) { + $tData = $this->token->getToken($strToken, 'change_pw'); + $tExists = $this->token->doesTokenExist('change_pw', $userID); + if (!is_array($tData) && $tExists == false) { + // token doesn't exist, let's create one, send an email with a link to use it, and error out + $token = $this->token->createToken('change_pw', $userID); + $aData['token'] = $token; + $aData['username'] = $this->getUserName($userID); + $aData['email'] = $this->getUserEmail($aData['username']); + $aData['subject'] = 'Account password change confirmation'; + $this->mail->sendMail('notifications/change_pw', $aData); + $this->setErrorMessage("A confirmation has been sent to your e-mail"); + return false; + } else { + // already exists, if it's valid delete it and allow this edit + if ($strToken === $tData['token']) { + $this->token->deleteToken($tData['token']); + } else { + // token exists for this type, but this is not the right token + $this->setErrorMessage("A confirmation was sent to your e-mail, follow that link to change your password"); + return false; + } + } + } $current = $this->getHash($current); $new = $this->getHash($new1); $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE ( id = ? AND pass = ? )"); @@ -294,12 +320,12 @@ class User extends Base { * @param address string new coin address * @param threshold float auto payout threshold * @param donat float donation % of income + * @param strToken string Token for confirmation * @return bool **/ - public function updateAccount($userID, $address, $threshold, $donate, $email, $is_anonymous) { + public function updateAccount($userID, $address, $threshold, $donate, $email, $is_anonymous, $strToken) { $this->debug->append("STA " . __METHOD__, 4); $bUser = false; - // number validation checks if (!is_numeric($threshold)) { $this->setErrorMessage('Invalid input for auto-payout'); @@ -347,6 +373,32 @@ class User extends Base { $threshold = min($this->config['ap_threshold']['max'], max(0, floatval($threshold))); $donate = min(100, max(0, floatval($donate))); + // twofactor - if details enabled we need to create/check the token + if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['details']) { + $tData = $this->token->getToken($strToken, 'account_edit'); + $tExists = $this->token->doesTokenExist('account_edit', $userID); + if (!is_array($tData) && $tExists == false) { + // token doesn't exist, let's create one, send an email with a link to use it, and error out + $token = $this->token->createToken('account_edit', $userID); + $aData['token'] = $token; + $aData['username'] = $this->getUserName($userID); + $aData['email'] = $this->getUserEmail($aData['username']); + $aData['subject'] = 'Account detail change confirmation'; + $this->mail->sendMail('notifications/account_edit', $aData); + $this->setErrorMessage("A confirmation has been sent to your e-mail"); + return false; + } else { + // already exists, if it's valid delete it and allow this edit + if ($strToken === $tData['token']) { + $this->token->deleteToken($tData['token']); + } else { + // token exists for this type, but this is not the right token + $this->setErrorMessage("A confirmation was sent to your e-mail, follow that link to edit your account details"); + return false; + } + } + } + // We passed all validation checks so update the account $stmt = $this->mysqli->prepare("UPDATE $this->table SET coin_address = ?, ap_threshold = ?, donate_percent = ?, email = ?, is_anonymous = ? WHERE id = ?"); if ($this->checkStmt($stmt) && $stmt->bind_param('sddsii', $address, $threshold, $donate, $email, $is_anonymous, $userID) && $stmt->execute()) diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 85dac842..57ac68ba 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -99,6 +99,30 @@ $config['coldwallet']['address'] = ''; $config['coldwallet']['reserve'] = 50; $config['coldwallet']['threshold'] = 5; +/** + * E-mail confirmations for user actions + * + * Explanation: + * To increase security for users, account detail changes can require + * an e-mail confirmation prior to performing certain actions. + * + * Options: + * enabled : Whether or not to require e-mail confirmations + * details : Require confirmation to change account details + * withdraw : Require confirmation to manually withdraw/payout + * changepw : Require confirmation to change password + * + * Default: + * enabled = true + * details = true + * withdraw = true + * changepw = true + */ +$config['twofactor']['enabled'] = true; +$config['twofactor']['details'] = true; +$config['twofactor']['withdraw'] = true; +$config['twofactor']['changepw'] = true; + /** * Lock account after maximum failed logins * diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index e368ea92..b69baebd 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -25,10 +25,11 @@ if ($user->isAuthenticated()) { $dBalance = $aBalance['confirmed']; if ($dBalance > $config['txfee_manual']) { if (!$oPayout->isPayoutActive($_SESSION['USERDATA']['id'])) { - if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'])) { + $wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token']; + if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'], $wf_token)) { $_SESSION['POPUP'][] = array('CONTENT' => 'Created new manual payout request with ID #' . $iPayoutId); } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to create manual payout request.', 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $iPayoutId->getError(), 'TYPE' => 'errormsg'); } } else { $_SESSION['POPUP'][] = array('CONTENT' => 'You already have one active manual payout request.', 'TYPE' => 'errormsg'); @@ -40,7 +41,8 @@ if ($user->isAuthenticated()) { break; case 'updateAccount': - if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'])) { + $ea_token = (!isset($_POST['ea_token'])) ? '' : $_POST['ea_token']; + if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'], $ea_token)) { $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account: ' . $user->getError(), 'TYPE' => 'errormsg'); @@ -48,7 +50,8 @@ if ($user->isAuthenticated()) { break; case 'updatePassword': - if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'])) { + $cp_token = (!isset($_POST['cp_token'])) ? '' : $_POST['cp_token']; + if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'], $cp_token)) { $_SESSION['POPUP'][] = array('CONTENT' => 'Password updated', 'TYPE' => 'success'); } else { $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); diff --git a/public/templates/mail/notifications/account_edit.tpl b/public/templates/mail/notifications/account_edit.tpl new file mode 100644 index 00000000..fa1fcfcf --- /dev/null +++ b/public/templates/mail/notifications/account_edit.tpl @@ -0,0 +1,9 @@ + + +

You have a pending request to change your account details.

+

If you initiated this request, please follow the link below to confirm your changes. If you did NOT, please notify an administrator.

+

http://{$smarty.server.SERVER_NAME}{$smarty.server.SCRIPT_NAME}?page=account&action=edit&ea_token={nocache}{$DATA.token}{/nocache}

+
+
+ + \ No newline at end of file diff --git a/public/templates/mail/notifications/change_pw.tpl b/public/templates/mail/notifications/change_pw.tpl new file mode 100644 index 00000000..7f0b8f63 --- /dev/null +++ b/public/templates/mail/notifications/change_pw.tpl @@ -0,0 +1,9 @@ + + +

You have a pending request to change your password.

+

If you initiated this request, please follow the link below to confirm your changes. If you did NOT, please notify an administrator.

+

http://{$smarty.server.SERVER_NAME}{$smarty.server.SCRIPT_NAME}?page=account&action=edit&cp_token={nocache}{$DATA.token}{/nocache}

+
+
+ + \ No newline at end of file diff --git a/public/templates/mail/notifications/withdraw_funds.tpl b/public/templates/mail/notifications/withdraw_funds.tpl new file mode 100644 index 00000000..ee17365c --- /dev/null +++ b/public/templates/mail/notifications/withdraw_funds.tpl @@ -0,0 +1,9 @@ + + +

You have a pending request to manually withdraw funds.

+

If you initiated this request, please follow the link below to confirm your changes. If you did NOT, please notify an administrator.

+

http://{$smarty.server.SERVER_NAME}{$smarty.server.SCRIPT_NAME}?page=account&action=edit&wf_token={nocache}{$DATA.token}{/nocache}

+
+
+ + \ No newline at end of file diff --git a/public/templates/mpos/account/edit/default.tpl b/public/templates/mpos/account/edit/default.tpl index 291581f2..1134ec2f 100644 --- a/public/templates/mpos/account/edit/default.tpl +++ b/public/templates/mpos/account/edit/default.tpl @@ -55,6 +55,7 @@ @@ -89,6 +90,7 @@ @@ -127,6 +129,7 @@ diff --git a/sql/000_base_structure.sql b/sql/000_base_structure.sql index 2d6e1789..eebe28ff 100644 --- a/sql/000_base_structure.sql +++ b/sql/000_base_structure.sql @@ -200,7 +200,10 @@ INSERT INTO `token_types` (`id`, `name`, `expiration`) VALUES (1, 'password_reset', 3600), (2, 'confirm_email', 0), (3, 'invitation', 0), -(4, 'account_unlock', 0); +(4, 'account_unlock', 0), +(5, 'account_edit', 360), +(6, 'change_pw', 360), +(7, 'withdraw_funds', 360); CREATE TABLE IF NOT EXISTS `transactions` ( `id` int(255) NOT NULL AUTO_INCREMENT, @@ -230,3 +233,4 @@ CREATE TABLE IF NOT EXISTS `templates` ( /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; + diff --git a/sql/013_tokentype_update.sql b/sql/013_tokentype_update.sql new file mode 100644 index 00000000..6c4e5c46 --- /dev/null +++ b/sql/013_tokentype_update.sql @@ -0,0 +1,3 @@ +INSERT INTO `token_types` (`name`, `expiration`) VALUES ('account_edit', 360); +INSERT INTO `token_types` (`name`, `expiration`) VALUES ('change_pw', 360); +INSERT INTO `token_types` (`name`, `expiration`) VALUES ('withdraw_funds', 360); From bfd803ec2835cf3ce9b562c397ce3890cedc915c Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 03:07:43 -0500 Subject: [PATCH 02/23] Incremented version, moved config options, return vals fixed in 2f checks --- public/include/classes/payout.class.php | 4 ++-- public/include/classes/user.class.php | 8 ++++---- public/include/config/global.inc.dist.php | 6 +++--- public/include/version.inc.php | 1 - sql/013_tokentype_update.sql | 2 ++ 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/public/include/classes/payout.class.php b/public/include/classes/payout.class.php index 96cd5141..02c7ae84 100644 --- a/public/include/classes/payout.class.php +++ b/public/include/classes/payout.class.php @@ -38,7 +38,7 @@ class Payout Extends Base { **/ public function createPayout($account_id=NULL, $strToken) { // twofactor - if cashout enabled we need to create/check the token - if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['withdraw']) { + if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['withdraw']) { $tData = $this->token->getToken($strToken, 'withdraw_funds'); $tExists = $this->token->doesTokenExist('withdraw_funds', $account_id); if (!is_array($tData) && $tExists == false) { @@ -50,7 +50,7 @@ class Payout Extends Base { $aData['subject'] = 'Manual payout request confirmation'; $this->mail->sendMail('notifications/withdraw_funds', $aData); $this->setErrorMessage("A confirmation has been sent to your e-mail"); - return false; + return true; } else { // already exists, if it's valid delete it and allow this edit if ($strToken === $tData['token']) { diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 9f026d59..99275636 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -275,7 +275,7 @@ class User extends Base { return false; } // twofactor - if changepw is enabled we need to create/check the token - if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['changepw']) { + if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['changepw']) { $tData = $this->token->getToken($strToken, 'change_pw'); $tExists = $this->token->doesTokenExist('change_pw', $userID); if (!is_array($tData) && $tExists == false) { @@ -287,7 +287,7 @@ class User extends Base { $aData['subject'] = 'Account password change confirmation'; $this->mail->sendMail('notifications/change_pw', $aData); $this->setErrorMessage("A confirmation has been sent to your e-mail"); - return false; + return true; } else { // already exists, if it's valid delete it and allow this edit if ($strToken === $tData['token']) { @@ -374,7 +374,7 @@ class User extends Base { $donate = min(100, max(0, floatval($donate))); // twofactor - if details enabled we need to create/check the token - if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['details']) { + if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['details']) { $tData = $this->token->getToken($strToken, 'account_edit'); $tExists = $this->token->doesTokenExist('account_edit', $userID); if (!is_array($tData) && $tExists == false) { @@ -386,7 +386,7 @@ class User extends Base { $aData['subject'] = 'Account detail change confirmation'; $this->mail->sendMail('notifications/account_edit', $aData); $this->setErrorMessage("A confirmation has been sent to your e-mail"); - return false; + return true; } else { // already exists, if it's valid delete it and allow this edit if ($strToken === $tData['token']) { diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 57ac68ba..07c20eb0 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -119,9 +119,9 @@ $config['coldwallet']['threshold'] = 5; * changepw = true */ $config['twofactor']['enabled'] = true; -$config['twofactor']['details'] = true; -$config['twofactor']['withdraw'] = true; -$config['twofactor']['changepw'] = true; +$config['twofactor']['options']['details'] = true; +$config['twofactor']['options']['withdraw'] = true; +$config['twofactor']['options']['changepw'] = true; /** * Lock account after maximum failed logins diff --git a/public/include/version.inc.php b/public/include/version.inc.php index f7feed1f..ffc53d5c 100644 --- a/public/include/version.inc.php +++ b/public/include/version.inc.php @@ -3,7 +3,6 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); -define('DB_VERSION', '0.0.2'); define('CONFIG_VERSION', '0.0.3'); define('MPOS_VERSION', '0.0.1'); diff --git a/sql/013_tokentype_update.sql b/sql/013_tokentype_update.sql index 6c4e5c46..d1e09e20 100644 --- a/sql/013_tokentype_update.sql +++ b/sql/013_tokentype_update.sql @@ -1,3 +1,5 @@ INSERT INTO `token_types` (`name`, `expiration`) VALUES ('account_edit', 360); INSERT INTO `token_types` (`name`, `expiration`) VALUES ('change_pw', 360); INSERT INTO `token_types` (`name`, `expiration`) VALUES ('withdraw_funds', 360); +INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.3') ON DUPLICATE KEY UPDATE `value` = '0.0.3'; +INSERT INTO `settings` (`name`, `value`) VALUES ('db_upgrade_required', 0) ON DUPLICATE KEY UPDATE `value` = 0; From 69eec05cb7737d6b731b9af89141a1ac04df8131 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 04:33:17 -0500 Subject: [PATCH 03/23] simplified notifications with index, updated the settings method, and fixed up template, sql fixes --- public/include/classes/notification.class.php | 22 +++++-------------- .../mpos/account/notifications/default.tpl | 21 ++++++++++++++---- sql/000_base_structure.sql | 2 +- sql/013_tokentype_update.sql | 2 ++ 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/public/include/classes/notification.class.php b/public/include/classes/notification.class.php index b11f990c..3db6acf8 100644 --- a/public/include/classes/notification.class.php +++ b/public/include/classes/notification.class.php @@ -112,28 +112,16 @@ class Notification extends Mail { $this->debug->append("STA " . __METHOD__, 4); $failed = $ok = 0; foreach ($data as $type => $active) { - // Does an entry exist already - $stmt = $this->mysqli->prepare("SELECT * FROM $this->tableSettings WHERE account_id = ? AND type = ?"); - if ($stmt && $stmt->bind_param('is', $account_id, $type) && $stmt->execute() && $stmt->store_result() && $stmt->num_rows() > 0) { - // We found a matching row - $stmt = $this->mysqli->prepare("UPDATE $this->tableSettings SET active = ? WHERE type = ? AND account_id = ?"); - if ($stmt && $stmt->bind_param('isi', $active, $type, $account_id) && $stmt->execute() && $stmt->close()) { - $ok++; - } else { - $failed++; - } + $stmt = $this->mysqli->prepare("INSERT INTO $this->tableSettings (active, type, account_id) VALUES (?,?,?) ON DUPLICATE KEY UPDATE active = ?"); + if ($stmt && $stmt->bind_param('isii', $active, $type, $account_id, $active) && $stmt->execute()) { + $ok++; } else { - $stmt = $this->mysqli->prepare("INSERT INTO $this->tableSettings (active, type, account_id) VALUES (?,?,?)"); - if ($stmt && $stmt->bind_param('isi', $active, $type, $account_id) && $stmt->execute()) { - $ok++; - } else { - $failed++; - } + $failed++; } } if ($failed > 0) { $this->setErrorMessage($this->getErrorMsg('E0047', $failed)); - return false; + return $this->sqlError(); } return true; } diff --git a/public/templates/mpos/account/notifications/default.tpl b/public/templates/mpos/account/notifications/default.tpl index 2d1f5901..b0003f6a 100644 --- a/public/templates/mpos/account/notifications/default.tpl +++ b/public/templates/mpos/account/notifications/default.tpl @@ -18,7 +18,7 @@ @@ -30,7 +30,7 @@ @@ -42,7 +42,7 @@ @@ -54,7 +54,19 @@ + + + + + Successful Login + + + @@ -94,6 +106,7 @@ {else if $NOTIFICATIONS[notification].type == auto_payout}Auto Payout {else if $NOTIFICATIONS[notification].type == idle_worker}IDLE Worker {else if $NOTIFICATIONS[notification].type == manual_payout}Manual Payout +{else if $NOTIFICATIONS[notification].type == success_login}Successful Login {/if} diff --git a/sql/000_base_structure.sql b/sql/000_base_structure.sql index eebe28ff..f5f8685e 100644 --- a/sql/000_base_structure.sql +++ b/sql/000_base_structure.sql @@ -128,7 +128,7 @@ CREATE TABLE IF NOT EXISTS `settings` ( UNIQUE KEY `setting` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.2'); +INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.3'); CREATE TABLE IF NOT EXISTS `shares` ( `id` bigint(30) NOT NULL AUTO_INCREMENT, diff --git a/sql/013_tokentype_update.sql b/sql/013_tokentype_update.sql index d1e09e20..c19e2537 100644 --- a/sql/013_tokentype_update.sql +++ b/sql/013_tokentype_update.sql @@ -1,5 +1,7 @@ INSERT INTO `token_types` (`name`, `expiration`) VALUES ('account_edit', 360); INSERT INTO `token_types` (`name`, `expiration`) VALUES ('change_pw', 360); INSERT INTO `token_types` (`name`, `expiration`) VALUES ('withdraw_funds', 360); +CREATE INDEX `account_id` ON `notification_settings` (`account_id`); +CREATE UNIQUE INDEX `account_id_type` ON `notification_settings` (`account_id`,`type`); INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.3') ON DUPLICATE KEY UPDATE `value` = '0.0.3'; INSERT INTO `settings` (`name`, `value`) VALUES ('db_upgrade_required', 0) ON DUPLICATE KEY UPDATE `value` = 0; From d9d678be612df112ff5b55017c16090071f59a21 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 09:58:00 -0500 Subject: [PATCH 04/23] retooled most of the email confirmation setup --- public/include/classes/payout.class.php | 28 +--- public/include/classes/token.class.php | 16 ++- public/include/classes/user.class.php | 93 ++++++------ public/include/config/global.inc.dist.php | 2 +- public/include/pages/account/edit.inc.php | 133 +++++++++++++----- public/include/smarty_globals.inc.php | 1 + public/include/version.inc.php | 4 +- public/site_assets/mpos/css/layout.css | 11 ++ .../templates/mpos/account/edit/default.tpl | 75 ++++++++-- sql/000_base_structure.sql | 6 +- sql/013_tokentype_update.sql | 2 +- 11 files changed, 248 insertions(+), 123 deletions(-) diff --git a/public/include/classes/payout.class.php b/public/include/classes/payout.class.php index 02c7ae84..c8298668 100644 --- a/public/include/classes/payout.class.php +++ b/public/include/classes/payout.class.php @@ -37,29 +37,14 @@ class Payout Extends Base { * @return data mixed Inserted ID or false **/ public function createPayout($account_id=NULL, $strToken) { - // twofactor - if cashout enabled we need to create/check the token + // twofactor - consume the token if it is enabled and valid if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['withdraw']) { - $tData = $this->token->getToken($strToken, 'withdraw_funds'); - $tExists = $this->token->doesTokenExist('withdraw_funds', $account_id); - if (!is_array($tData) && $tExists == false) { - // token doesn't exist, let's create one, send an email with a link to use it, and error out - $token = $this->token->createToken('withdraw_funds', $account_id); - $aData['token'] = $token; - $aData['username'] = $this->getUserName($account_id); - $aData['email'] = $this->getUserEmail($aData['username']); - $aData['subject'] = 'Manual payout request confirmation'; - $this->mail->sendMail('notifications/withdraw_funds', $aData); - $this->setErrorMessage("A confirmation has been sent to your e-mail"); - return true; + $tValid = $this->token->isTokenValid($account_id, $strToken, 7); + if ($tValid) { + $this->token->deleteToken($strToken); } else { - // already exists, if it's valid delete it and allow this edit - if ($strToken === $tData['token']) { - $this->token->deleteToken($tData['token']); - } else { - // token exists for this type, but this is not the right token - $this->setErrorMessage("A confirmation was sent to your e-mail, follow that link to cash out"); - return false; - } + $this->setErrorMessage('Invalid token'); + return false; } } $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id) VALUES (?)"); @@ -86,7 +71,6 @@ $oPayout = new Payout(); $oPayout->setDebug($debug); $oPayout->setMysql($mysqli); $oPayout->setConfig($config); -$oPayout->setMail($mail); $oPayout->setToken($oToken); $oPayout->setErrorCodes($aErrorCodes); diff --git a/public/include/classes/token.class.php b/public/include/classes/token.class.php index ca68ebc7..bb947005 100644 --- a/public/include/classes/token.class.php +++ b/public/include/classes/token.class.php @@ -22,6 +22,20 @@ class Token Extends Base { return $this->sqlError(); } + /** + * Check if a token we're passing in is completely valid + * @param account_id int Account id of user + * @param token string Token to check + * @param type int Type of token + * @return int 0 or 1 + */ + public function isTokenValid($account_id, $token, $type) { + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE account_id = ? AND token = ? AND type = ? AND time < NOW() LIMIT 1"); + if ($stmt && $stmt->bind_param('isi', $account_id, $token, $type) && $stmt->execute()) + return $stmt->get_result()->num_rows; + return $this->sqlError(); + } + /** * Check if a token of this type already exists for a given account_id * @param strType string Name of the type of token @@ -35,7 +49,7 @@ class Token Extends Base { } $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE account_id = ? AND type = ? LIMIT 1"); if ($stmt && $stmt->bind_param('ii', $account_id, $iToken_id) && $stmt->execute()) - return $stmt->num_rows; + return $stmt->get_result()->num_rows; return $this->sqlError(); } diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 99275636..7e36b54a 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -255,6 +255,43 @@ class User extends Base { return $dPercent; } + /** + * Send e-mail to confirm a change for 2fa + * @param strType string Token type name + * @param userID int User ID + * @return bool + */ + public function sendChangeConf($strType, $userID) { + $exists = $this->token->doesTokenExist($strType, $userID); + if ($exists == 0) { + $token = $this->token->createToken($strType, $userID); + $aData['token'] = $token; + $aData['username'] = $this->getUserName($userID); + $aData['email'] = $this->getUserEmail($aData['username']); + switch ($strType) { + case 'account_edit': + $aData['subject'] = 'Account detail change confirmation'; + break; + case 'change_pw': + $aData['subject'] = 'Account password change confirmation'; + break; + case 'withdraw_funds': + $aData['subject'] = 'Manual payout request confirmation'; + break; + default: + $aData['subject'] = ''; + } + if ($this->mail->sendMail('notifications/'.$strType, $aData)) { + return true; + } else { + $this->setErrorMessage('Failed to send the notification'); + return false; + } + } + $this->setErrorMessage('A request has already been sent to your e-mail address. Please wait 10 minutes for it to expire.'); + return false; + } + /** * Update the accounts password * @param userID int User ID @@ -274,29 +311,14 @@ class User extends Base { $this->setErrorMessage( 'New password is too short, please use more than 8 chars' ); return false; } - // twofactor - if changepw is enabled we need to create/check the token + // twofactor - consume the token if it is enabled and valid if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['changepw']) { - $tData = $this->token->getToken($strToken, 'change_pw'); - $tExists = $this->token->doesTokenExist('change_pw', $userID); - if (!is_array($tData) && $tExists == false) { - // token doesn't exist, let's create one, send an email with a link to use it, and error out - $token = $this->token->createToken('change_pw', $userID); - $aData['token'] = $token; - $aData['username'] = $this->getUserName($userID); - $aData['email'] = $this->getUserEmail($aData['username']); - $aData['subject'] = 'Account password change confirmation'; - $this->mail->sendMail('notifications/change_pw', $aData); - $this->setErrorMessage("A confirmation has been sent to your e-mail"); - return true; + $tValid = $this->token->isTokenValid($userID, $strToken, 6); + if ($tValid) { + $this->token->deleteToken($strToken); } else { - // already exists, if it's valid delete it and allow this edit - if ($strToken === $tData['token']) { - $this->token->deleteToken($tData['token']); - } else { - // token exists for this type, but this is not the right token - $this->setErrorMessage("A confirmation was sent to your e-mail, follow that link to change your password"); - return false; - } + $this->setErrorMessage('Invalid token'); + return false; } } $current = $this->getHash($current); @@ -313,7 +335,7 @@ class User extends Base { $this->setErrorMessage( 'Unable to update password, current password wrong?' ); return false; } - + /** * Update account information from the edit account page * @param userID int User ID @@ -373,29 +395,14 @@ class User extends Base { $threshold = min($this->config['ap_threshold']['max'], max(0, floatval($threshold))); $donate = min(100, max(0, floatval($donate))); - // twofactor - if details enabled we need to create/check the token + // twofactor - consume the token if it is enabled and valid if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['details']) { - $tData = $this->token->getToken($strToken, 'account_edit'); - $tExists = $this->token->doesTokenExist('account_edit', $userID); - if (!is_array($tData) && $tExists == false) { - // token doesn't exist, let's create one, send an email with a link to use it, and error out - $token = $this->token->createToken('account_edit', $userID); - $aData['token'] = $token; - $aData['username'] = $this->getUserName($userID); - $aData['email'] = $this->getUserEmail($aData['username']); - $aData['subject'] = 'Account detail change confirmation'; - $this->mail->sendMail('notifications/account_edit', $aData); - $this->setErrorMessage("A confirmation has been sent to your e-mail"); - return true; + $tValid = $this->token->isTokenValid($userID, $strToken, 5); + if ($tValid) { + $this->token->deleteToken($strToken); } else { - // already exists, if it's valid delete it and allow this edit - if ($strToken === $tData['token']) { - $this->token->deleteToken($tData['token']); - } else { - // token exists for this type, but this is not the right token - $this->setErrorMessage("A confirmation was sent to your e-mail, follow that link to edit your account details"); - return false; - } + $this->setErrorMessage('Invalid token'); + return false; } } diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 07c20eb0..13daf7da 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -7,7 +7,7 @@ if (!defined('SECURITY')) die('Hacking attempt'); * This is used in the version check to ensure you run the latest version of the configuration file. * Once you upgraded your config, change the version here too. **/ -$config['version'] = '0.0.3'; +$config['version'] = '0.0.4'; // Our include directory for additional features define('INCLUDE_DIR', BASEPATH . 'include'); diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index b69baebd..95bbf74f 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -4,7 +4,44 @@ if (!defined('SECURITY')) die('Hacking attempt'); +// 2fa tpl stuff +$cp_editable = $wf_editable = $ea_editable = $wf_sent = $ea_sent = $cp_sent = 0; +$ea_token = (!isset($_GET['ea_token'])) ? '' : $_GET['ea_token']; +$cp_token = (!isset($_GET['cp_token'])) ? '' : $_GET['cp_token']; +$wf_token = (!isset($_GET['wf_token'])) ? '' : $_GET['wf_token']; if ($user->isAuthenticated()) { + // update 2f tpl stuff + if ($config['twofactor']['enabled']) { + $popupmsg = 'E-mail confirmations are required for '; + $popuptypes = array(); + if ($config['twofactor']['options']['details']) { + $popuptypes[] = 'editing your details'; + $ea_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $ea_token, 5); + $ea_sent = $user->token->doesTokenExist('account_edit', $_SESSION['USERDATA']['id']); + } + if ($config['twofactor']['options']['changepw']) { + $popuptypes[] = 'changing your password'; + $cp_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $cp_token, 6); + $cp_sent = $user->token->doesTokenExist('change_pw', $_SESSION['USERDATA']['id']); + } + if ($config['twofactor']['options']['withdraw']) { + $popuptypes[] = 'withdrawals'; + $wf_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $wf_token, 7); + $wf_sent = $user->token->doesTokenExist('withdraw_funds', $_SESSION['USERDATA']['id']); + } + $ptc = 0; + $ptcn = count($popuptypes); + foreach ($popuptypes as $pt) { + if ($ptcn == 1) { $popupmsg.= $popuptypes[$ptc]; continue; } + if ($ptc !== ($ptcn-1)) { + $popupmsg.= $popuptypes[$ptc].', '; + } else { + $popupmsg.= 'and '.$popuptypes[$ptc]; + } + $ptc++; + } + $_SESSION['POPUP'][] = array('CONTENT' => $popupmsg, 'TYPE' => 'info'); + } if (isset($_POST['do']) && $_POST['do'] == 'genPin') { if ($user->generatePin($_SESSION['USERDATA']['id'], $_POST['currentPassword'])) { $_SESSION['POPUP'][] = array('CONTENT' => 'Your PIN # has been sent to your email.', 'TYPE' => 'success'); @@ -13,54 +50,78 @@ if ($user->isAuthenticated()) { } } else { - if ( @$_POST['do'] && (! $user->checkPin($_SESSION['USERDATA']['id'], @$_POST['authPin']))) { + if ( @$_POST['do'] && (!$checkpin = $user->checkPin($_SESSION['USERDATA']['id'], @$_POST['authPin']))) { $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid PIN. ' . ($config['maxfailed']['pin'] - $user->getUserPinFailed($_SESSION['USERDATA']['id'])) . ' attempts remaining.', 'TYPE' => 'errormsg'); } else { - switch (@$_POST['do']) { - case 'cashOut': - if ($setting->getValue('disable_payouts') == 1 || $setting->getValue('disable_manual_payouts') == 1) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Manual payouts are disabled.', 'TYPE' => 'info'); - } else { - $aBalance = $transaction->getBalance($_SESSION['USERDATA']['id']); - $dBalance = $aBalance['confirmed']; - if ($dBalance > $config['txfee_manual']) { - if (!$oPayout->isPayoutActive($_SESSION['USERDATA']['id'])) { - $wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token']; - if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'], $wf_token)) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Created new manual payout request with ID #' . $iPayoutId); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => $iPayoutId->getError(), 'TYPE' => 'errormsg'); - } - } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'You already have one active manual payout request.', 'TYPE' => 'errormsg'); - } + if (isset($_POST['unlock']) && isset($_POST['utype']) && $checkpin) { + $validtypes = array('account_edit','change_pw','withdraw_funds'); + $isvalid = in_array($_POST['utype'],$validtypes); + if ($isvalid) { + $ctype = strip_tags($_POST['utype']); + $send = $user->sendChangeConf($ctype, $_SESSION['USERDATA']['id']); + // set to sent for this pageload + if ($ctype == 'account_edit') $ea_sent = 1; + if ($ctype == 'change_pw') $cp_sent = 1; + if ($ctype == 'withdraw_funds') $wf_sent = 1; + if ($send) { + $_SESSION['POPUP'][] = array('CONTENT' => 'A confirmation was sent to your e-mail, follow that link to continue', 'TYPE' => 'success'); } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Insufficient funds, you need more than ' . $config['txfee_manual'] . ' ' . $config['currency'] . ' to cover transaction fees', 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); } } - break; - - case 'updateAccount': + } else { $ea_token = (!isset($_POST['ea_token'])) ? '' : $_POST['ea_token']; - if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'], $ea_token)) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account: ' . $user->getError(), 'TYPE' => 'errormsg'); - } - break; - - case 'updatePassword': $cp_token = (!isset($_POST['cp_token'])) ? '' : $_POST['cp_token']; - if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'], $cp_token)) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Password updated', 'TYPE' => 'success'); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); + $wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token']; + switch (@$_POST['do']) { + case 'cashOut': + if ($setting->getValue('disable_payouts') == 1 || $setting->getValue('disable_manual_payouts') == 1) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Manual payouts are disabled.', 'TYPE' => 'info'); + } else { + $aBalance = $transaction->getBalance($_SESSION['USERDATA']['id']); + $dBalance = $aBalance['confirmed']; + if ($dBalance > $config['txfee']) { + if (!$oPayout->isPayoutActive($_SESSION['USERDATA']['id'])) { + if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'], $wf_token)) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Created new manual payout request with ID #' . $iPayoutId); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $iPayoutId->getError(), 'TYPE' => 'errormsg'); + } + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'You already have one active manual payout request.', 'TYPE' => 'errormsg'); + } + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Insufficient funds, you need more than ' . $config['txfee'] . ' ' . $config['currency'] . ' to cover transaction fees', 'TYPE' => 'errormsg'); + } + } + break; + + case 'updateAccount': + if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'], $ea_token)) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account: ' . $user->getError(), 'TYPE' => 'errormsg'); + } + break; + + case 'updatePassword': + if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'], $cp_token)) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Password updated', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); + } + break; } - break; } } } } // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); +$smarty->assign("CHANGEPASSUNLOCKED", $cp_editable); +$smarty->assign("WITHDRAWUNLOCKED", $wf_editable); +$smarty->assign("DETAILSUNLOCKED", $ea_editable); +$smarty->assign("CHANGEPASSSENT", $cp_sent); +$smarty->assign("WITHDRAWSENT", $wf_sent); +$smarty->assign("DETAILSSENT", $ea_sent); ?> diff --git a/public/include/smarty_globals.inc.php b/public/include/smarty_globals.inc.php index 01eeacbf..21fbcf2a 100644 --- a/public/include/smarty_globals.inc.php +++ b/public/include/smarty_globals.inc.php @@ -64,6 +64,7 @@ $aGlobal = array( 'confirmations' => $config['confirmations'], 'reward' => $config['reward'], 'price' => $setting->getValue('price'), + 'twofactor' => $config['twofactor'], 'config' => array( 'disable_navbar' => $setting->getValue('disable_navbar'), 'disable_navbar_api' => $setting->getValue('disable_navbar_api'), diff --git a/public/include/version.inc.php b/public/include/version.inc.php index ffc53d5c..6db42c6c 100644 --- a/public/include/version.inc.php +++ b/public/include/version.inc.php @@ -3,8 +3,8 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); -define('CONFIG_VERSION', '0.0.3'); -define('MPOS_VERSION', '0.0.1'); +define('DB_VERSION', '0.0.4'); +define('CONFIG_VERSION', '0.0.4'); // Fetch installed database version $db_version = $setting->getValue('DB_VERSION'); diff --git a/public/site_assets/mpos/css/layout.css b/public/site_assets/mpos/css/layout.css index f28a2a70..d44651ee 100644 --- a/public/site_assets/mpos/css/layout.css +++ b/public/site_assets/mpos/css/layout.css @@ -580,6 +580,17 @@ text-shadow: 0 1px 0 #6CDCF9; cursor: pointer; } +input[type=submit].alt_btn:disabled,input[type=submit].alt_btn:readonly { +background: #D0D1D4 url(../images/btn_submit.png) repeat-x; +border: 1px solid#aaa; +text-shadow: none; +color: #999; +} + +input[type=submit].alt_btn:disabled:hover { +color: #999; +} + input[type=submit].alt_btn:hover { color: #001217; } diff --git a/public/templates/mpos/account/edit/default.tpl b/public/templates/mpos/account/edit/default.tpl index 1134ec2f..c63e6ccc 100644 --- a/public/templates/mpos/account/edit/default.tpl +++ b/public/templates/mpos/account/edit/default.tpl @@ -7,11 +7,11 @@
- +
- +
{if !$GLOBAL.website.api.disabled}
@@ -21,29 +21,29 @@ {/if}
- +
- +
Donation amount in percent (example: 0.5) - +

{$GLOBAL.config.ap_threshold.min}-{$GLOBAL.config.ap_threshold.max} {$GLOBAL.config.currency}. Set to '0' for no auto payout. A {if $GLOBAL.config.txfee_auto > 0.00001}{$GLOBAL.config.txfee_auto}{else}{$GLOBAL.config.txfee_auto|number_format:"8"}{/if}% {$GLOBAL.config.currency} TX fee will apply - +
Hide username on website from others. Admins can still get your user information.
@@ -55,8 +55,23 @@
@@ -77,11 +92,11 @@

- +
- +
@@ -90,8 +105,23 @@
@@ -112,15 +142,15 @@

- +
- +
- +
@@ -129,8 +159,23 @@
diff --git a/sql/000_base_structure.sql b/sql/000_base_structure.sql index f5f8685e..f34db926 100644 --- a/sql/000_base_structure.sql +++ b/sql/000_base_structure.sql @@ -96,7 +96,9 @@ CREATE TABLE IF NOT EXISTS `notification_settings` ( `type` varchar(15) NOT NULL, `account_id` int(11) NOT NULL, `active` tinyint(1) NOT NULL DEFAULT '0', - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `account_id` (`account_id`), + UNIQUE KEY `account_id_type` (`account_id`,`type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `payouts` ( @@ -128,7 +130,7 @@ CREATE TABLE IF NOT EXISTS `settings` ( UNIQUE KEY `setting` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.3'); +INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.4'); CREATE TABLE IF NOT EXISTS `shares` ( `id` bigint(30) NOT NULL AUTO_INCREMENT, diff --git a/sql/013_tokentype_update.sql b/sql/013_tokentype_update.sql index c19e2537..fe66e238 100644 --- a/sql/013_tokentype_update.sql +++ b/sql/013_tokentype_update.sql @@ -3,5 +3,5 @@ INSERT INTO `token_types` (`name`, `expiration`) VALUES ('change_pw', 360); INSERT INTO `token_types` (`name`, `expiration`) VALUES ('withdraw_funds', 360); CREATE INDEX `account_id` ON `notification_settings` (`account_id`); CREATE UNIQUE INDEX `account_id_type` ON `notification_settings` (`account_id`,`type`); -INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.3') ON DUPLICATE KEY UPDATE `value` = '0.0.3'; +INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.4') ON DUPLICATE KEY UPDATE `value` = '0.0.4'; INSERT INTO `settings` (`name`, `value`) VALUES ('db_upgrade_required', 0) ON DUPLICATE KEY UPDATE `value` = 0; From 96b734edaa7be00ee08c1ae61813f578b84cabca Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 10:35:24 -0500 Subject: [PATCH 05/23] fix how late we delete tokens for 2fa --- public/include/classes/payout.class.php | 20 ++++++------ public/include/classes/user.class.php | 41 ++++++++++++------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/public/include/classes/payout.class.php b/public/include/classes/payout.class.php index c8298668..ce4368e8 100644 --- a/public/include/classes/payout.class.php +++ b/public/include/classes/payout.class.php @@ -37,18 +37,18 @@ class Payout Extends Base { * @return data mixed Inserted ID or false **/ public function createPayout($account_id=NULL, $strToken) { - // twofactor - consume the token if it is enabled and valid - if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['withdraw']) { - $tValid = $this->token->isTokenValid($account_id, $strToken, 7); - if ($tValid) { - $this->token->deleteToken($strToken); - } else { - $this->setErrorMessage('Invalid token'); - return false; - } - } $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id) VALUES (?)"); if ($stmt && $stmt->bind_param('i', $account_id) && $stmt->execute()) { + // twofactor - consume the token if it is enabled and valid + if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['withdraw']) { + $tValid = $this->token->isTokenValid($account_id, $strToken, 7); + if ($tValid) { + $this->token->deleteToken($strToken); + } else { + $this->setErrorMessage('Invalid token'); + return false; + } + } return $stmt->insert_id; } return $this->sqlError('E0049'); diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 7e36b54a..0f08164c 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -311,16 +311,6 @@ class User extends Base { $this->setErrorMessage( 'New password is too short, please use more than 8 chars' ); return false; } - // twofactor - consume the token if it is enabled and valid - if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['changepw']) { - $tValid = $this->token->isTokenValid($userID, $strToken, 6); - if ($tValid) { - $this->token->deleteToken($strToken); - } else { - $this->setErrorMessage('Invalid token'); - return false; - } - } $current = $this->getHash($current); $new = $this->getHash($new1); $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE ( id = ? AND pass = ? )"); @@ -328,6 +318,16 @@ class User extends Base { $stmt->bind_param('sis', $new, $userID, $current); $stmt->execute(); if ($stmt->errno == 0 && $stmt->affected_rows === 1) { + // twofactor - consume the token if it is enabled and valid + if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['changepw']) { + $tValid = $this->token->isTokenValid($userID, $strToken, 6); + if ($tValid) { + $this->token->deleteToken($strToken); + } else { + $this->setErrorMessage('Invalid token'); + return false; + } + } return true; } $stmt->close(); @@ -395,20 +395,19 @@ class User extends Base { $threshold = min($this->config['ap_threshold']['max'], max(0, floatval($threshold))); $donate = min(100, max(0, floatval($donate))); - // twofactor - consume the token if it is enabled and valid - if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['details']) { - $tValid = $this->token->isTokenValid($userID, $strToken, 5); - if ($tValid) { - $this->token->deleteToken($strToken); - } else { - $this->setErrorMessage('Invalid token'); - return false; - } - } - // We passed all validation checks so update the account $stmt = $this->mysqli->prepare("UPDATE $this->table SET coin_address = ?, ap_threshold = ?, donate_percent = ?, email = ?, is_anonymous = ? WHERE id = ?"); if ($this->checkStmt($stmt) && $stmt->bind_param('sddsii', $address, $threshold, $donate, $email, $is_anonymous, $userID) && $stmt->execute()) + // twofactor - consume the token if it is enabled and valid + if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['details']) { + $tValid = $this->token->isTokenValid($userID, $strToken, 5); + if ($tValid) { + $this->token->deleteToken($strToken); + } else { + $this->setErrorMessage('Invalid token'); + return false; + } + } return true; // Catchall $this->setErrorMessage('Failed to update your account'); From a0ecbd0294ead18a534b7498a2d91a4a163eb8e7 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 10:52:54 -0500 Subject: [PATCH 06/23] fix cosmetic issue --- public/include/pages/account/edit.inc.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index 95bbf74f..1d5eaf50 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -83,6 +83,7 @@ if ($user->isAuthenticated()) { if ($dBalance > $config['txfee']) { if (!$oPayout->isPayoutActive($_SESSION['USERDATA']['id'])) { if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'], $wf_token)) { + $wf_sent = 0; $wf_editable = 0; $_SESSION['POPUP'][] = array('CONTENT' => 'Created new manual payout request with ID #' . $iPayoutId); } else { $_SESSION['POPUP'][] = array('CONTENT' => $iPayoutId->getError(), 'TYPE' => 'errormsg'); @@ -98,6 +99,7 @@ if ($user->isAuthenticated()) { case 'updateAccount': if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'], $ea_token)) { + $ea_sent = 0; $ea_editable = 0; $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account: ' . $user->getError(), 'TYPE' => 'errormsg'); @@ -106,6 +108,7 @@ if ($user->isAuthenticated()) { case 'updatePassword': if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'], $cp_token)) { + $cp_sent = 0; $cp_editable = 0; $_SESSION['POPUP'][] = array('CONTENT' => 'Password updated', 'TYPE' => 'success'); } else { $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); From 1b1f552567b220ac088a08389b0ac064eea6f806 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 11:04:40 -0500 Subject: [PATCH 07/23] fix cosmetic issue #2 --- public/include/pages/account/edit.inc.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index 1d5eaf50..035ca73d 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -83,7 +83,7 @@ if ($user->isAuthenticated()) { if ($dBalance > $config['txfee']) { if (!$oPayout->isPayoutActive($_SESSION['USERDATA']['id'])) { if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'], $wf_token)) { - $wf_sent = 0; $wf_editable = 0; + $wf_sent = 0; $_SESSION['POPUP'][] = array('CONTENT' => 'Created new manual payout request with ID #' . $iPayoutId); } else { $_SESSION['POPUP'][] = array('CONTENT' => $iPayoutId->getError(), 'TYPE' => 'errormsg'); @@ -99,7 +99,7 @@ if ($user->isAuthenticated()) { case 'updateAccount': if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'], $ea_token)) { - $ea_sent = 0; $ea_editable = 0; + $ea_sent = 0; $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account: ' . $user->getError(), 'TYPE' => 'errormsg'); @@ -108,7 +108,7 @@ if ($user->isAuthenticated()) { case 'updatePassword': if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'], $cp_token)) { - $cp_sent = 0; $cp_editable = 0; + $cp_sent = 0; $_SESSION['POPUP'][] = array('CONTENT' => 'Password updated', 'TYPE' => 'success'); } else { $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); From a598eec9241315ede9ceee05fd20dc3ffc7d8bd8 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 11:09:00 -0500 Subject: [PATCH 08/23] fix sync changes done in edit.inc.php at the end of request --- public/include/pages/account/edit.inc.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index 035ca73d..1100eefd 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -119,6 +119,21 @@ if ($user->isAuthenticated()) { } } } +// one last time so we can sync with changes we made during this page +if ($config['twofactor']['enabled']) { + if ($config['twofactor']['options']['details']) { + $ea_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $ea_token, 5); + $ea_sent = $user->token->doesTokenExist('account_edit', $_SESSION['USERDATA']['id']); + } + if ($config['twofactor']['options']['changepw']) { + $cp_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $cp_token, 6); + $cp_sent = $user->token->doesTokenExist('change_pw', $_SESSION['USERDATA']['id']); + } + if ($config['twofactor']['options']['withdraw']) { + $wf_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $wf_token, 7); + $wf_sent = $user->token->doesTokenExist('withdraw_funds', $_SESSION['USERDATA']['id']); + } +} // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); $smarty->assign("CHANGEPASSUNLOCKED", $cp_editable); From 40d09a4ee431d9a12ff4f8f56956b3806dd760bb Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 11:10:36 -0500 Subject: [PATCH 09/23] oops, forgot to make sure we're auth'ed --- public/include/pages/account/edit.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index 1100eefd..94a225c4 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -120,7 +120,7 @@ if ($user->isAuthenticated()) { } } // one last time so we can sync with changes we made during this page -if ($config['twofactor']['enabled']) { +if ($user->isAuthenticated() && $config['twofactor']['enabled']) { if ($config['twofactor']['options']['details']) { $ea_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $ea_token, 5); $ea_sent = $user->token->doesTokenExist('account_edit', $_SESSION['USERDATA']['id']); From ed8349ef5042bbae632ce56c7fe671868c299529 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 11:47:44 -0500 Subject: [PATCH 10/23] works as far as I can tell --- public/include/pages/account/edit.inc.php | 10 +++++ public/site_assets/mpos/css/layout.css | 2 +- .../templates/mpos/account/edit/default.tpl | 42 ++++++++----------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index 94a225c4..d775b6c0 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -121,6 +121,16 @@ if ($user->isAuthenticated()) { } // one last time so we can sync with changes we made during this page if ($user->isAuthenticated() && $config['twofactor']['enabled']) { + // stupid little hack because different request types + if (@$_POST['do'] !== 'genPin' || isset($_POST['unlock'])) { + $ea_token = (!isset($_GET['ea_token'])) ? '' : $_GET['ea_token']; + $cp_token = (!isset($_GET['cp_token'])) ? '' : $_GET['cp_token']; + $wf_token = (!isset($_GET['wf_token'])) ? '' : $_GET['wf_token']; + } else { + $ea_token = (!isset($_POST['ea_token'])) ? '' : $_POST['ea_token']; + $cp_token = (!isset($_POST['cp_token'])) ? '' : $_POST['cp_token']; + $wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token']; + } if ($config['twofactor']['options']['details']) { $ea_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $ea_token, 5); $ea_sent = $user->token->doesTokenExist('account_edit', $_SESSION['USERDATA']['id']); diff --git a/public/site_assets/mpos/css/layout.css b/public/site_assets/mpos/css/layout.css index d44651ee..8ea0107b 100644 --- a/public/site_assets/mpos/css/layout.css +++ b/public/site_assets/mpos/css/layout.css @@ -580,7 +580,7 @@ text-shadow: 0 1px 0 #6CDCF9; cursor: pointer; } -input[type=submit].alt_btn:disabled,input[type=submit].alt_btn:readonly { +input[type=submit].alt_btn:disabled { background: #D0D1D4 url(../images/btn_submit.png) repeat-x; border: 1px solid#aaa; text-shadow: none; diff --git a/public/templates/mpos/account/edit/default.tpl b/public/templates/mpos/account/edit/default.tpl index c63e6ccc..91ea0038 100644 --- a/public/templates/mpos/account/edit/default.tpl +++ b/public/templates/mpos/account/edit/default.tpl @@ -59,14 +59,12 @@ {if $GLOBAL.twofactor.enabled && $GLOBAL.twofactor.options.details} - {if $DETAILSUNLOCKED > 0 && $DETAILSSENT > 0} - - {else} - {if $DETAILSSENT == 1} - - {else} - - {/if} + {if $DETAILSSENT == 1 && $DETAILSUNLOCKED == 1} + + {elseif $DETAILSSENT == 0 && $DETAILSUNLOCKED == 1 || $DETAILSSENT == 1 && $DETAILSUNLOCKED == 0} + + {elseif $DETAILSSENT == 0 && $DETAILSUNLOCKED == 0} + {/if} {else} @@ -109,14 +107,12 @@ {if $GLOBAL.twofactor.enabled && $GLOBAL.twofactor.options.withdraw} - {if $WITHDRAWUNLOCKED > 0 && $WITHDRAWSENT > 0} - - {else} - {if $WITHDRAWSENT == 1} - - {else} - - {/if} + {if $WITHDRAWSENT == 1 && $WITHDRAWUNLOCKED == 1} + + {elseif $WITHDRAWSENT == 0 && $WITHDRAWUNLOCKED == 1 || $WITHDRAWSENT == 1 && $WITHDRAWUNLOCKED == 0} + + {elseif $WITHDRAWSENT == 0 && $WITHDRAWUNLOCKED == 0} + {/if} {else} @@ -163,14 +159,12 @@ {if $GLOBAL.twofactor.enabled && $GLOBAL.twofactor.options.changepw} - {if $CHANGEPASSUNLOCKED > 0 && $CHANGEPASSSENT > 0} - - {else} - {if $CHANGEPASSSENT == 1} - - {else} - - {/if} + {if $CHANGEPASSSENT == 1 && $CHANGEPASSUNLOCKED == 1} + + {elseif $CHANGEPASSSENT == 0 && $CHANGEPASSUNLOCKED == 1 || $CHANGEPASSSENT == 1 && $CHANGEPASSUNLOCKED == 0} + + {elseif $CHANGEPASSSENT == 0 && $CHANGEPASSUNLOCKED == 0} + {/if} {else} From 802930cba158fcdeff9883815ff607012841aabd Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 13:58:14 -0500 Subject: [PATCH 11/23] save old token to use in case we error out --- public/include/pages/account/edit.inc.php | 65 ++++++++++++++--------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index d775b6c0..fd819f51 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -4,13 +4,27 @@ if (!defined('SECURITY')) die('Hacking attempt'); -// 2fa tpl stuff $cp_editable = $wf_editable = $ea_editable = $wf_sent = $ea_sent = $cp_sent = 0; -$ea_token = (!isset($_GET['ea_token'])) ? '' : $_GET['ea_token']; -$cp_token = (!isset($_GET['cp_token'])) ? '' : $_GET['cp_token']; -$wf_token = (!isset($_GET['wf_token'])) ? '' : $_GET['wf_token']; + +// stupid hack to fix input when an error happened with a valid token +$ea_token = (!isset($_POST['ea_token'])) ? '' : $_POST['ea_token']; +$cp_token = (!isset($_POST['cp_token'])) ? '' : $_POST['cp_token']; +$wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token']; +// set old token and type so we can use it later +$old_token = ""; +$old_token_type = 0; +if ($ea_token !== "") { + $old_token = $ea_token; + $old_token_type = 5; +} else if ($wf_token !== "") { + $old_token = $wf_token; + $old_token_type = 7; +} else if ($cp_token !== "") { + $old_token_type = 6; + $old_token = $cp_token; +} + if ($user->isAuthenticated()) { - // update 2f tpl stuff if ($config['twofactor']['enabled']) { $popupmsg = 'E-mail confirmations are required for '; $popuptypes = array(); @@ -59,10 +73,6 @@ if ($user->isAuthenticated()) { if ($isvalid) { $ctype = strip_tags($_POST['utype']); $send = $user->sendChangeConf($ctype, $_SESSION['USERDATA']['id']); - // set to sent for this pageload - if ($ctype == 'account_edit') $ea_sent = 1; - if ($ctype == 'change_pw') $cp_sent = 1; - if ($ctype == 'withdraw_funds') $wf_sent = 1; if ($send) { $_SESSION['POPUP'][] = array('CONTENT' => 'A confirmation was sent to your e-mail, follow that link to continue', 'TYPE' => 'success'); } else { @@ -70,9 +80,18 @@ if ($user->isAuthenticated()) { } } } else { - $ea_token = (!isset($_POST['ea_token'])) ? '' : $_POST['ea_token']; - $cp_token = (!isset($_POST['cp_token'])) ? '' : $_POST['cp_token']; - $wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token']; + // back to get, was only post to fix for stupid hack + $ea_token = (!isset($_GET['ea_token'])) ? '' : $_GET['ea_token']; + $cp_token = (!isset($_GET['cp_token'])) ? '' : $_GET['cp_token']; + $wf_token = (!isset($_GET['wf_token'])) ? '' : $_GET['wf_token']; + if ($ea_token == '' && isset($_POST['ea_token']) && strlen($_POST['ea_token']) > 1) { + $ea_token = $_POST['ea_token']; + } else if ($ea_token == '' && isset($_POST['cp_token']) && strlen($_POST['cp_token']) > 1) { + $cp_token = $_POST['cp_token']; + } else if ($wf_token == '' && isset($_POST['wf_token']) && strlen($_POST['wf_token']) > 1) { + $wf_token = $_POST['wf_token']; + } + switch (@$_POST['do']) { case 'cashOut': if ($setting->getValue('disable_payouts') == 1 || $setting->getValue('disable_manual_payouts') == 1) { @@ -83,7 +102,6 @@ if ($user->isAuthenticated()) { if ($dBalance > $config['txfee']) { if (!$oPayout->isPayoutActive($_SESSION['USERDATA']['id'])) { if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'], $wf_token)) { - $wf_sent = 0; $_SESSION['POPUP'][] = array('CONTENT' => 'Created new manual payout request with ID #' . $iPayoutId); } else { $_SESSION['POPUP'][] = array('CONTENT' => $iPayoutId->getError(), 'TYPE' => 'errormsg'); @@ -99,7 +117,6 @@ if ($user->isAuthenticated()) { case 'updateAccount': if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'], $ea_token)) { - $ea_sent = 0; $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account: ' . $user->getError(), 'TYPE' => 'errormsg'); @@ -108,7 +125,6 @@ if ($user->isAuthenticated()) { case 'updatePassword': if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'], $cp_token)) { - $cp_sent = 0; $_SESSION['POPUP'][] = array('CONTENT' => 'Password updated', 'TYPE' => 'success'); } else { $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); @@ -119,17 +135,16 @@ if ($user->isAuthenticated()) { } } } -// one last time so we can sync with changes we made during this page +// 2fa - one last time so we can sync with changes we made during this page if ($user->isAuthenticated() && $config['twofactor']['enabled']) { - // stupid little hack because different request types - if (@$_POST['do'] !== 'genPin' || isset($_POST['unlock'])) { - $ea_token = (!isset($_GET['ea_token'])) ? '' : $_GET['ea_token']; - $cp_token = (!isset($_GET['cp_token'])) ? '' : $_GET['cp_token']; - $wf_token = (!isset($_GET['wf_token'])) ? '' : $_GET['wf_token']; - } else { - $ea_token = (!isset($_POST['ea_token'])) ? '' : $_POST['ea_token']; - $cp_token = (!isset($_POST['cp_token'])) ? '' : $_POST['cp_token']; - $wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token']; + // stupid hack part deux + // set the token to be the old token so we still have it if it errors out + if ($old_token_type == 5) { + $ea_token = $old_token; + } else if ($old_token_type == 7) { + $wf_token = $old_token; + } else if ($old_token_type == 6) { + $cp_token = $old_token; } if ($config['twofactor']['options']['details']) { $ea_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $ea_token, 5); From 741b6464ef37da046b781f0520e5ea67858c8c95 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 14:10:43 -0500 Subject: [PATCH 12/23] success_login tpl for new notification --- public/templates/mail/notifications/success_login.tpl | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 public/templates/mail/notifications/success_login.tpl diff --git a/public/templates/mail/notifications/success_login.tpl b/public/templates/mail/notifications/success_login.tpl new file mode 100644 index 00000000..c1e4926f --- /dev/null +++ b/public/templates/mail/notifications/success_login.tpl @@ -0,0 +1,8 @@ + + +

Your account has successfully logged in

+

If you initiated the login you can ignore this message. If you did NOT, please notify an administrator.

+
+
+ + \ No newline at end of file From f3a6d65eab2140dfeecce08d6d48e741c9ac61eb Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 14:45:10 -0500 Subject: [PATCH 13/23] send notifications on successful login when active --- public/include/classes/user.class.php | 27 +++++++++++++++++-- .../mail/notifications/success_login.tpl | 2 +- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 0f08164c..47ec49cb 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -130,8 +130,30 @@ class User extends Base { if ($this->checkUserPassword($username, $password)) { $this->updateLoginTimestamp($this->getUserId($username)); $this->createSession($username); - if ($this->setUserIp($this->getUserId($username), $_SERVER['REMOTE_ADDR'])) + if ($this->setUserIp($this->getUserId($username), $_SERVER['REMOTE_ADDR'])) { + // send a notification if success_login is active + $uid = $this->getUserId($username); + $notifs = new Notification(); + $notifs->setDebug($this->debug); + $notifs->setMysql($this->mysqli); + $notifs->setSmarty($this->smarty); + $notifs->setConfig($this->config); + $notifs->setSetting($this->setting); + $notifs->setErrorCodes($this->aErrorCodes); + $ndata = $notifs->getNotificationSettings($uid); + if (is_array($ndata)) { + foreach ($ndata as $nd) { + if ($nd['type'] == 'success_login' && $nd['active'] == 1) { + // seems to be active, let's send it + $aDataN['username'] = $username; + $aDataN['email'] = $this->getUserEmail($username); + $aDataN['subject'] = 'Successful login notification'; + $notifs->sendNotification($uid, 'success_login', $aDataN); + } + } + } return true; + } } $this->setErrorMessage("Invalid username or password"); if ($id = $this->getUserId($username)) { @@ -142,7 +164,7 @@ class User extends Base { if ($token = $this->token->createToken('account_unlock', $id)) { $aData['token'] = $token; $aData['username'] = $username; - $aData['email'] = $this->getUserEmail($username);; + $aData['email'] = $this->getUserEmail($username); $aData['subject'] = 'Account auto-locked'; $this->mail->sendMail('notifications/locked', $aData); } @@ -777,6 +799,7 @@ $user = new User(); $user->setDebug($debug); $user->setMysql($mysqli); $user->setSalt(SALT); +$user->setSmarty($smarty); $user->setConfig($config); $user->setMail($mail); $user->setToken($oToken); diff --git a/public/templates/mail/notifications/success_login.tpl b/public/templates/mail/notifications/success_login.tpl index c1e4926f..8dc5345a 100644 --- a/public/templates/mail/notifications/success_login.tpl +++ b/public/templates/mail/notifications/success_login.tpl @@ -1,6 +1,6 @@ -

Your account has successfully logged in

+

Your account was successfully logged in

If you initiated the login you can ignore this message. If you did NOT, please notify an administrator.



From 8ed8338b3e41bfd4347ea359b4316d477a7c27e6 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 14:50:45 -0500 Subject: [PATCH 14/23] fixed my incorrect use of notif settings array --- public/include/classes/user.class.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 47ec49cb..ba07b950 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -141,16 +141,12 @@ class User extends Base { $notifs->setSetting($this->setting); $notifs->setErrorCodes($this->aErrorCodes); $ndata = $notifs->getNotificationSettings($uid); - if (is_array($ndata)) { - foreach ($ndata as $nd) { - if ($nd['type'] == 'success_login' && $nd['active'] == 1) { - // seems to be active, let's send it - $aDataN['username'] = $username; - $aDataN['email'] = $this->getUserEmail($username); - $aDataN['subject'] = 'Successful login notification'; - $notifs->sendNotification($uid, 'success_login', $aDataN); - } - } + if ($ndata['success_login'] == 1) { + // seems to be active, let's send it + $aDataN['username'] = $username; + $aDataN['email'] = $this->getUserEmail($username); + $aDataN['subject'] = 'Successful login notification'; + $notifs->sendNotification($uid, 'success_login', $aDataN); } return true; } From dc984aca636e883c2b6545896ffc26ae2aa25f41 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 15:21:06 -0500 Subject: [PATCH 15/23] fixed gitignore for eclipse, added templates/compile/mpos folder and a blank file to fix issues with setup guide/chowning compile dir --- .gitignore | 3 +++ public/templates/compile/mpos/deleteme.txt | 1 + 2 files changed, 4 insertions(+) create mode 100644 public/templates/compile/mpos/deleteme.txt diff --git a/.gitignore b/.gitignore index 91f343f8..3c86c5f0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ public/include/config/global.inc.sha.php # IDE Settings /.idea/* +/.buildpath/* +/.project/* +/.settings/* diff --git a/public/templates/compile/mpos/deleteme.txt b/public/templates/compile/mpos/deleteme.txt new file mode 100644 index 00000000..8d1c8b69 --- /dev/null +++ b/public/templates/compile/mpos/deleteme.txt @@ -0,0 +1 @@ + From 9d14902bb56d607b63a1e69f02b42a4f8b73f8dd Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 16:18:59 -0500 Subject: [PATCH 16/23] fix nocache in account/edit template --- public/templates/mpos/account/edit/default.tpl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/public/templates/mpos/account/edit/default.tpl b/public/templates/mpos/account/edit/default.tpl index 91ea0038..4e5c779b 100644 --- a/public/templates/mpos/account/edit/default.tpl +++ b/public/templates/mpos/account/edit/default.tpl @@ -21,16 +21,16 @@ {/if}
- + {nocache}{/nocache}
- + {nocache}{/nocache}
Donation amount in percent (example: 0.5) - + {nocache}{/nocache}
@@ -43,7 +43,7 @@ Hide username on website from others. Admins can still get your user information.
@@ -90,11 +90,11 @@

- + {nocache}{/nocache}
- + {nocache}{/nocache}
@@ -138,15 +138,15 @@

- + {nocache}{/nocache}
- + {nocache}{/nocache}
- + {nocache}{/nocache}
From bae30b2e4f08c9fb71bb3bc30b3edb67412590a1 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 16:29:32 -0500 Subject: [PATCH 17/23] fixed success_login tpl verbiage --- public/templates/mail/notifications/success_login.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/templates/mail/notifications/success_login.tpl b/public/templates/mail/notifications/success_login.tpl index 8dc5345a..df92934b 100644 --- a/public/templates/mail/notifications/success_login.tpl +++ b/public/templates/mail/notifications/success_login.tpl @@ -1,7 +1,7 @@ -

Your account was successfully logged in

-

If you initiated the login you can ignore this message. If you did NOT, please notify an administrator.

+

Your account has successfully logged in

+

If you initiated this login, you can ignore this message. If you did NOT, please notify an administrator.



From 2d0938b35bc3c27f54cde6d65e79e7798951d785 Mon Sep 17 00:00:00 2001 From: xisi Date: Wed, 15 Jan 2014 19:32:33 -0500 Subject: [PATCH 18/23] [ADDED] Simple CSRF protection tokens * Adds config options for disabling, timeout lead time, and forms * Adds another salt in config that's used in the token * Adds protection for login form by default --- .gitignore | 6 ++-- public/include/classes/base.class.php | 3 ++ public/include/classes/user.class.php | 22 ++++++++++++++ public/include/config/global.inc.dist.php | 23 +++++++++++++++ public/include/pages/account/edit.inc.php | 6 ++-- public/include/pages/home.inc.php | 7 ++++- public/include/pages/login.inc.php | 35 ++++++++++++++++++----- public/include/smarty_globals.inc.php | 1 + public/templates/mpos/login/default.tpl | 1 + public/templates/mpos/login/small.tpl | 1 + 10 files changed, 90 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 3c86c5f0..15e9bfc9 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,6 @@ public/include/config/global.inc.sha.php # IDE Settings /.idea/* -/.buildpath/* -/.project/* -/.settings/* +.buildpath +.project +.settings diff --git a/public/include/classes/base.class.php b/public/include/classes/base.class.php index 85b43d57..d3dd18c1 100644 --- a/public/include/classes/base.class.php +++ b/public/include/classes/base.class.php @@ -31,6 +31,9 @@ class Base { public function setSalt($salt) { $this->salt = $salt; } + public function setSalty($salt) { + $this->salty = $salt; + } public function setSmarty($smarty) { $this->smarty = $smarty; } diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index ba07b950..de2a3266 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -788,6 +788,27 @@ class User extends Base { if ($logout == true) $this->logoutUser($_SERVER['REQUEST_URI']); return false; } + + /** + * Gets the current CSRF token for this user/type setting and time chunk + * @param string User; for hash seed, if username isn't available use IP + * @param string Type of token; for hash seed, should be unique per page/use + * @return string CSRF token + */ + public function getCSRFToken($user, $type) { + $date = date('m/d/y/H/i/s'); + $data = explode('/', $date); + $month = $data[0]; $day = $data[1]; $year = $data[2]; + $hour = $data[3]; $minute = $data[4]; $second = $data[5]; + $seed = $this->salty; + // X second lead time on each minute + if ($minute == 59 && $second > (60-$this->config['csrf']['options']['leadtime'])) { + $minute = 0; + $fhour = ($hour == 23) ? $hour = 0 : $hour+=1; + } + $seed = $seed.$month.$day.$user.$type.$year.$hour.$minute.$seed; + return $this->getHash($seed); + } } // Make our class available automatically @@ -795,6 +816,7 @@ $user = new User(); $user->setDebug($debug); $user->setMysql($mysqli); $user->setSalt(SALT); +$user->setSalty(SALTY); $user->setSmarty($smarty); $user->setConfig($config); $user->setMail($mail); diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 13daf7da..3c6cddae 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -26,6 +26,7 @@ define('DEBUG', 0); // SALT used to hash passwords define('SALT', 'PLEASEMAKEMESOMETHINGRANDOM'); +define('SALTY', 'THISSHOULDALSOBERRAANNDDOOM'); /** * Underlying coin algorithm that you are mining on. Set this to whatever your coin needs: @@ -123,6 +124,28 @@ $config['twofactor']['options']['details'] = true; $config['twofactor']['options']['withdraw'] = true; $config['twofactor']['options']['changepw'] = true; +/** + * CSRF protection config + * + * Explanation: + * To help protect against CSRF, we can generate a hash that changes every minute + * and is unique for each user/IP and page or use, and check against that when + * the form is submitted. + * + * Options: + * enabled = Whether or not we will generate/check for valid CSRF tokens + * leadtime = 1 minute + leadtime seconds for reseeding tokens + * login = Use and check CSRF tokens for the login forms + * + * Default: + * enabled = true + * leadtime = 3 + * login = true + */ +$config['csrf']['enabled'] = true; +$config['csrf']['options']['leadtime'] = 3; +$config['csrf']['forms']['login'] = true; + /** * Lock account after maximum failed logins * diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index fd819f51..b3614a87 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -4,9 +4,8 @@ if (!defined('SECURITY')) die('Hacking attempt'); +// twofactor stuff $cp_editable = $wf_editable = $ea_editable = $wf_sent = $ea_sent = $cp_sent = 0; - -// stupid hack to fix input when an error happened with a valid token $ea_token = (!isset($_POST['ea_token'])) ? '' : $_POST['ea_token']; $cp_token = (!isset($_POST['cp_token'])) ? '' : $_POST['cp_token']; $wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token']; @@ -80,7 +79,7 @@ if ($user->isAuthenticated()) { } } } else { - // back to get, was only post to fix for stupid hack + // back to get, was only post to fix for old token $ea_token = (!isset($_GET['ea_token'])) ? '' : $_GET['ea_token']; $cp_token = (!isset($_GET['cp_token'])) ? '' : $_GET['cp_token']; $wf_token = (!isset($_GET['wf_token'])) ? '' : $_GET['wf_token']; @@ -137,7 +136,6 @@ if ($user->isAuthenticated()) { } // 2fa - one last time so we can sync with changes we made during this page if ($user->isAuthenticated() && $config['twofactor']['enabled']) { - // stupid hack part deux // set the token to be the old token so we still have it if it errors out if ($old_token_type == 5) { $ea_token = $old_token; diff --git a/public/include/pages/home.inc.php b/public/include/pages/home.inc.php index ea85e43b..4dffeb8a 100644 --- a/public/include/pages/home.inc.php +++ b/public/include/pages/home.inc.php @@ -22,7 +22,12 @@ if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { } else { $debug->append('Using cached page', 3); } - +// csrf token - update if it's enabled +$token = ''; +if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) { + $token = $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login'); +} // Load news entries for Desktop site and unauthenticated users $smarty->assign("CONTENT", "default.tpl"); +$smarty->assign('CTOKEN', $token); ?> diff --git a/public/include/pages/login.inc.php b/public/include/pages/login.inc.php index e7cbaffa..f1758eb4 100644 --- a/public/include/pages/login.inc.php +++ b/public/include/pages/login.inc.php @@ -5,16 +5,37 @@ if (!defined('SECURITY')) die('Hacking attempt'); if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserId($_POST['username']))) { $_SESSION['POPUP'][] = array('CONTENT' => 'You are not allowed to login during maintenace.', 'TYPE' => 'info'); -} else if ($user->checkLogin(@$_POST['username'], @$_POST['password']) ) { - empty($_POST['to']) ? $to = $_SERVER['SCRIPT_NAME'] : $to = $_POST['to']; - $port = ($_SERVER["SERVER_PORT"] == "80" or $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); - $location = @$_SERVER['HTTPS'] === true ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $to : 'http://' . $_SERVER['SERVER_NAME'] . $port . $to; - if (!headers_sent()) header('Location: ' . $location); - exit(''); +} else if (isset($_POST['username']) && isset($_POST['password'])) { + $nocsrf = 1; + if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) { + if ((isset($_POST['ctoken']) && $_POST['ctoken'] !== $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login')) || (!isset($_POST['ctoken']))) { + // csrf protection is on and this token is invalid, error out -> time expired + $nocsrf = 0; + } + } + if ($nocsrf == 1 || (!$config['csrf']['enabled'] || !$config['csrf']['forms']['login'])) { + $checklogin = $user->checkLogin($_POST['username'], $_POST['password']); + if ($checklogin) { + empty($_POST['to']) ? $to = $_SERVER['SCRIPT_NAME'] : $to = $_POST['to']; + $port = ($_SERVER["SERVER_PORT"] == "80" or $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); + $location = @$_SERVER['HTTPS'] === true ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $to : 'http://' . $_SERVER['SERVER_NAME'] . $port . $to; + if (!headers_sent()) header('Location: ' . $location); + exit(''); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: '. $user->getError(), 'TYPE' => 'errormsg'); + } + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Login token expired'. $user->getError(), 'TYPE' => 'errormsg'); + } } else if (@$_POST['username'] && @$_POST['password']) { $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: '. $user->getError(), 'TYPE' => 'errormsg'); } - +// csrf token - update if it's enabled +$token = ''; +if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) { + $token = $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login'); +} // Load login template $smarty->assign('CONTENT', 'default.tpl'); +$smarty->assign('CTOKEN', $token); ?> diff --git a/public/include/smarty_globals.inc.php b/public/include/smarty_globals.inc.php index 21fbcf2a..f059d477 100644 --- a/public/include/smarty_globals.inc.php +++ b/public/include/smarty_globals.inc.php @@ -65,6 +65,7 @@ $aGlobal = array( 'reward' => $config['reward'], 'price' => $setting->getValue('price'), 'twofactor' => $config['twofactor'], + 'csrf' => $config['csrf'], 'config' => array( 'disable_navbar' => $setting->getValue('disable_navbar'), 'disable_navbar_api' => $setting->getValue('disable_navbar_api'), diff --git a/public/templates/mpos/login/default.tpl b/public/templates/mpos/login/default.tpl index 031b9ca0..9b8eefec 100644 --- a/public/templates/mpos/login/default.tpl +++ b/public/templates/mpos/login/default.tpl @@ -1,6 +1,7 @@
+ {if $GLOBAL.csrf.enabled && $GLOBAL.csrf.forms.login}{/if}

Login with existing account

diff --git a/public/templates/mpos/login/small.tpl b/public/templates/mpos/login/small.tpl index e02921de..6448f750 100644 --- a/public/templates/mpos/login/small.tpl +++ b/public/templates/mpos/login/small.tpl @@ -2,6 +2,7 @@