[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
This commit is contained in:
parent
409f41bc35
commit
ef904858ae
@ -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);
|
||||
|
||||
?>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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
|
||||
*
|
||||
|
||||
@ -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');
|
||||
|
||||
9
public/templates/mail/notifications/account_edit.tpl
Normal file
9
public/templates/mail/notifications/account_edit.tpl
Normal file
@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>You have a pending request to change your account details.</p>
|
||||
<p>If you initiated this request, please follow the link below to confirm your changes. If you did NOT, please notify an administrator.</p>
|
||||
<p>http://{$smarty.server.SERVER_NAME}{$smarty.server.SCRIPT_NAME}?page=account&action=edit&ea_token={nocache}{$DATA.token}{/nocache}</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</body>
|
||||
</html>
|
||||
9
public/templates/mail/notifications/change_pw.tpl
Normal file
9
public/templates/mail/notifications/change_pw.tpl
Normal file
@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>You have a pending request to change your password.</p>
|
||||
<p>If you initiated this request, please follow the link below to confirm your changes. If you did NOT, please notify an administrator.</p>
|
||||
<p>http://{$smarty.server.SERVER_NAME}{$smarty.server.SCRIPT_NAME}?page=account&action=edit&cp_token={nocache}{$DATA.token}{/nocache}</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</body>
|
||||
</html>
|
||||
9
public/templates/mail/notifications/withdraw_funds.tpl
Normal file
9
public/templates/mail/notifications/withdraw_funds.tpl
Normal file
@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>You have a pending request to manually withdraw funds.</p>
|
||||
<p>If you initiated this request, please follow the link below to confirm your changes. If you did NOT, please notify an administrator.</p>
|
||||
<p>http://{$smarty.server.SERVER_NAME}{$smarty.server.SCRIPT_NAME}?page=account&action=edit&wf_token={nocache}{$DATA.token}{/nocache}</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</body>
|
||||
</html>
|
||||
@ -55,6 +55,7 @@
|
||||
</div>
|
||||
<footer>
|
||||
<div class="submit_link">
|
||||
<input type="hidden" name="ea_token" value="{$smarty.request.ea_token|escape}">
|
||||
<input type="submit" value="Update Account" class="alt_btn">
|
||||
</div>
|
||||
</footer>
|
||||
@ -89,6 +90,7 @@
|
||||
</div>
|
||||
<footer>
|
||||
<div class="submit_link">
|
||||
<input type="hidden" name="wf_token" value="{$smarty.request.wf_token|escape}">
|
||||
<input type="submit" value="Cash Out" class="alt_btn">
|
||||
</div>
|
||||
</footer>
|
||||
@ -127,6 +129,7 @@
|
||||
</div>
|
||||
<footer>
|
||||
<div class="submit_link">
|
||||
<input type="hidden" name="cp_token" value="{$smarty.request.cp_token|escape}">
|
||||
<input type="submit" value="Change Password" class="alt_btn">
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -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 */;
|
||||
|
||||
|
||||
3
sql/013_tokentype_update.sql
Normal file
3
sql/013_tokentype_update.sql
Normal file
@ -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);
|
||||
Loading…
Reference in New Issue
Block a user