diff --git a/.gitignore b/.gitignore index 91f343f8..15e9bfc9 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/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/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/include/classes/payout.class.php b/public/include/classes/payout.class.php index d7c2cae1..ce4368e8 100644 --- a/public/include/classes/payout.class.php +++ b/public/include/classes/payout.class.php @@ -32,12 +32,23 @@ 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) { $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'); @@ -59,6 +70,8 @@ class Payout Extends Base { $oPayout = new Payout(); $oPayout->setDebug($debug); $oPayout->setMysql($mysqli); +$oPayout->setConfig($config); +$oPayout->setToken($oToken); $oPayout->setErrorCodes($aErrorCodes); ?> diff --git a/public/include/classes/token.class.php b/public/include/classes/token.class.php index 8453c245..bb947005 100644 --- a/public/include/classes/token.class.php +++ b/public/include/classes/token.class.php @@ -21,6 +21,37 @@ class Token Extends Base { return $result->fetch_assoc(); 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 + * @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->get_result()->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..ada25335 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -130,8 +130,26 @@ 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 ($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; + } } $this->setErrorMessage("Invalid username or password"); if ($id = $this->getUserId($username)) { @@ -142,7 +160,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); } @@ -255,15 +273,53 @@ 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 sendChangeConfigEmail($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 * @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' ); @@ -280,6 +336,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(); @@ -287,19 +353,19 @@ 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 * @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'); @@ -350,6 +416,16 @@ class User extends Base { // 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'); @@ -712,6 +788,29 @@ 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; + $lead = $this->config['csrf']['options']['leadtime']; + if ($lead >= 11) { $lead = 10; } + if ($lead <= 0) { $lead = 3; } + if ($minute == 59 && $second > (60-$lead)) { + $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 @@ -719,6 +818,8 @@ $user = new User(); $user->setDebug($debug); $user->setMysql($mysqli); $user->setSalt(SALT); +$user->setSalty(SALTY); +$user->setSmarty($smarty); $user->setConfig($config); $user->setMail($mail); $user->setToken($oToken); diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 85dac842..fddace83 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'); @@ -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: @@ -99,6 +100,52 @@ $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']['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 a + * form is submitted. + * + * Options: + * enabled = Whether or not we will generate/check for valid CSRF tokens + * leadtime = Length of time in seconds to give as leeway, 1-10s + * 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 e368ea92..8df98502 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -4,7 +4,57 @@ if (!defined('SECURITY')) die('Hacking attempt'); +// twofactor stuff +$cp_editable = $wf_editable = $ea_editable = $wf_sent = $ea_sent = $cp_sent = 0; +$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()) { + 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,51 +63,106 @@ 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'])) { - if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'])) { - $_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'); - } - } 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->sendChangeConfigEmail($ctype, $_SESSION['USERDATA']['id']); + 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': - if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'])) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account: ' . $user->getError(), 'TYPE' => 'errormsg'); + } else { + // 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']; + 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']; } - break; - - case 'updatePassword': - if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'])) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Password updated', 'TYPE' => 'success'); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); + + 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; } } } } +// 2fa - one last time so we can sync with changes we made during this page +if ($user->isAuthenticated() && $config['twofactor']['enabled']) { + // 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); + $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); +$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/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..5c26848d 100644 --- a/public/include/pages/login.inc.php +++ b/public/include/pages/login.inc.php @@ -5,16 +5,38 @@ 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 { + $img = ""; + $_SESSION['POPUP'][] = array('CONTENT' => "Login token expired, please try again $img", 'TYPE' => 'info'); + } } 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 01eeacbf..f059d477 100644 --- a/public/include/smarty_globals.inc.php +++ b/public/include/smarty_globals.inc.php @@ -64,6 +64,8 @@ $aGlobal = array( 'confirmations' => $config['confirmations'], '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/include/version.inc.php b/public/include/version.inc.php index f7feed1f..157f229b 100644 --- a/public/include/version.inc.php +++ b/public/include/version.inc.php @@ -3,9 +3,9 @@ // 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'); +define('DB_VERSION', '0.0.3'); +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..8ea0107b 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 { +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/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/success_login.tpl b/public/templates/mail/notifications/success_login.tpl new file mode 100644 index 00000000..df92934b --- /dev/null +++ b/public/templates/mail/notifications/success_login.tpl @@ -0,0 +1,8 @@ + + +

Your account has successfully logged in

+

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

+
+
+ + \ 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..4e5c779b 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}
- + {nocache}{/nocache}
- + {nocache}{/nocache}
Donation amount in percent (example: 0.5) - + {nocache}{/nocache}

{$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,7 +55,21 @@
@@ -76,11 +90,11 @@

- + {nocache}{/nocache}
- + {nocache}{/nocache}
@@ -89,7 +103,21 @@ @@ -110,15 +138,15 @@

- + {nocache}{/nocache}
- + {nocache}{/nocache}
- + {nocache}{/nocache}
@@ -127,7 +155,21 @@
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/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 @@