Skip to content

Commit

Permalink
Real email confirmation is here (issue 25). This is both an addition …
Browse files Browse the repository at this point in the history
…to the Admin CP and Client CP. Admins can now manage unconfirmed emails by going to Clients -> Email Confirmation. Users can check the status of their email or change it in their control panel under the Change Email navigation item. At this point in time having an unconfirmed email doesn't really do anything. In future releases we can use this info to automatically suspend or terminate accounts that have not validated their emails in a certain amount of time. Or suspend accounts immediately after signup until they have confirmed their email, much like Admin Validation is now. Confirm codes are just sha1 hashes of some data. Chance of collision is within acceptable limits. Even if hashes were to collide it wouldn't be disastrous as they're not logically required to be unique. This revision replaces the initial implementation of this idea by kujoez in r53-r55 and r58.

You can check on the status of a user's email from anywhere using the $main->getEmailStatus method. It returns an integer 0 meaning the email is confirmed, 1 meaning "accepted" or not confirmed but email validation is not required, 2 meaning the email is unconfirmed, and 3 meaning the email is unconfirmed because they're attempting to change their email.

When a user's account is initially suspended due to Admin Validation their account is suspended with the reason "TheHostingTool: Awaiting Admin Validation"

Also introduced the ability to re-order the Client CP navigation in the database through a weight column.
  • Loading branch information
kmark committed Oct 29, 2012
1 parent b7103e0 commit 20aca16
Show file tree
Hide file tree
Showing 17 changed files with 319 additions and 69 deletions.
57 changes: 56 additions & 1 deletion admin/pages/users.php
Expand Up @@ -18,7 +18,8 @@ public function __construct() {
$this->navtitle = "Clients Sub Menu";
$this->navlist[] = array("Search Clients", "magnifier.png", "search");
$this->navlist[] = array("Client Statistics", "book.png", "stats");
$this->navlist[] = array("Admin Validate", "user_suit.png", "validate");
$this->navlist[] = array("Admin Validation", "user_suit.png", "validate");
$this->navlist[] = array("Email Confirmation", "email.png", "emailconfirm");
}

public function description() {
Expand Down Expand Up @@ -391,6 +392,60 @@ public function content() { # Displays the page
echo $tpl;
}
break;

case "emailconfirm":
if(!(bool)$db->config("emailval")) {
$main->errors("Email confirmation is currently disabled. Please go to General Settings -> Signup Form to enable it.");
echo "<ERRORS>";
return;
}
switch ($_POST['action']) {
case 'resend':
global $email;
$result = $email->sendConfirmEmail((int)$_POST['id']);
if(is_array($result)) {
$main->errors('Confirmation email for <code>'.$result[0].'</code> has been resent to <code>'.$result[1].'</code><br />');
break;
}
$main->errors('Failed to resend email confirmation.<br />');
break;
case 'confirm':
if($server->confirm($_POST['id'], null, null, true)) {
$main->errors('Email manually confirmed.<br />');
break;
}
$main->errors('Manual email confirmation failed.<br />');
break;
}
$add = '';
$reason = '';
if($_GET['include'] == 'new') {
$add = '`emailval` = 0';
$reason = 'new accounts';
} elseif($_GET['include'] == 'changes') {
$add = '`newemail` IS NOT NULL';
$reason = 'new emails';
} else {
$add = '`emailval` = 0 OR `newemail` IS NOT NULL';
$reason = 'accounts/emails';
}
$query = $db->query("SELECT * FROM `<PRE>users` WHERE $add");
$e2 = '';
if($db->num_rows($query) == 0) {
$e2 = "<br /><br />There are no clients with $reason that have not been confirmed.";
if(!isset($reason)) { break; }
}
echo $style->replaceVar("tpl/admineconfirmlist.tpl", array('ERRORS2' => $e2));
while($data = $db->fetch_array($query)) {
$replace['USER'] = $data['user'];
$replace['EMAIL'] = $data['email'];
$replace['include'] = isset($_GET['include']) ? '&include='.$_GET['include'] : '';
$replace['NEWEMAIL'] = $data['newemail']===null?'Initial email confirmation. Not changing emails.':$data['newemail'];
$replace['SIGNUP'] = date('r', $data['signup']);
$replace['ID'] = $data['id'];
echo $style->replaceVar("tpl/admineconfirmentry.tpl", $replace);
}
break;
}
}
}
Expand Down
11 changes: 4 additions & 7 deletions client/confirm.php
Expand Up @@ -20,18 +20,15 @@
echo $style->get("header.tpl"); #Output Header
echo '<div align="center">';

if(!$main->getvar['u']) {
if(!$_REQUEST['i'] || !$_REQUEST['u'] || !$_REQUEST['c']) {
echo "Please use the link provided in your e-mail.";
}
else {
$username = $main->getvar['u'];
$confirm = $main->getvar['c'];
$command = $server->confirm($username, $confirm);
if($command == false) {
echo 'Confirmation failed, please try to copy and paste the link into your browser.';
if($server->confirm($_REQUEST['i'], $_REQUEST['c'], $_REQUEST['u'])) {
echo 'Email confirmed.';
}
else {
echo 'Account confirmed.';
echo "That user doesn't exist, has already been confirmed, or the confirmation code is invalid.";
}
}
echo '</div>'; #End it
Expand Down
4 changes: 2 additions & 2 deletions client/index.php
Expand Up @@ -40,10 +40,10 @@ function client() {
// Main Side Bar HTML
$nav = "Sidebar";
if(!$db->config("delacc")) {
$sub = $db->query("SELECT * FROM `<PRE>clientnav` WHERE `link` != 'delete'");
$sub = $db->query("SELECT * FROM `<PRE>clientnav` WHERE `link` != 'delete' ORDER BY `weight` ASC");
}
else {
$sub = $db->query("SELECT * FROM `<PRE>clientnav`");
$sub = $db->query("SELECT * FROM `<PRE>clientnav` ORDER BY `weight` ASC");
}
while($row = $db->fetch_array($sub)) {
$array2['IMGURL'] = $row['icon'];
Expand Down
13 changes: 0 additions & 13 deletions client/pages/details.php
Expand Up @@ -15,7 +15,6 @@ public function content() { # Displays the page
global $style, $db, $main;
$data = $db->client($_SESSION['cuser']);
$array['USER'] = $data['user'];
$array['EMAIL'] = $data['email'];
$array['DOMAIN'] = $data['domain'];
$array['FIRSTNAME'] = $data['firstname'];
$array['LASTNAME'] = $data['lastname'];
Expand All @@ -27,17 +26,6 @@ public function content() { # Displays the page
$array['PHONE'] = $data['phone'];
$array['DISP'] = "<div>";
if($_POST) {
if(!preg_match('/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i',$main->postvar['email'])) {
$main->errors("Your email is the wrong format!");
echo $style->replaceVar("tpl/cedit.tpl", $array);
return;
}
$query = $db->query("SELECT * FROM `<PRE>users` WHERE `email` = '{$main->postvar['email']}' AND `id` != '{$_SESSION['cuser']}'");
if($db->num_rows($query) != 0) {
$main->errors("That e-mail address is already in use!");
echo $style->replaceVar("tpl/cedit.tpl", $array);
return;
}
if(!$main->postvar['state']) {
$main->errors("Please enter a valid state!");
echo $style->replaceVar("tpl/cedit.tpl", $array);
Expand Down Expand Up @@ -98,7 +86,6 @@ public function content() { # Displays the page
echo $style->replaceVar("tpl/cedit.tpl", $array);
return;
}
$db->query("UPDATE `<PRE>users` SET `email` = '{$main->postvar['email']}' WHERE `id` = '{$_SESSION['cuser']}'");
$db->query("UPDATE `<PRE>users` SET `state` = '{$main->postvar['state']}' WHERE `id` = '{$_SESSION['cuser']}'");
$db->query("UPDATE `<PRE>users` SET `address` = '{$main->postvar['address']}' WHERE `id` = '{$_SESSION['cuser']}'");
$db->query("UPDATE `<PRE>users` SET `phone` = '{$main->postvar['phone']}' WHERE `id` = '{$_SESSION['cuser']}'");
Expand Down
60 changes: 60 additions & 0 deletions client/pages/email.php
@@ -0,0 +1,60 @@
<?php
if(THT != 1){die();}

class page {
public function content($passthrough = false) {
global $main, $db, $style, $email;
$id = $_SESSION['cuser'];
$status = $main->getEmailStatus($id);
$rep['STATUS'] = $style->notice(true, 'Confirmed');
$rep['RESEND'] = 'disabled="disabled"';
$rep['CANCEL'] = 'disabled="disabled"';
switch($status) {
case 1:
$rep['STATUS'] = $style->notice(true, 'Accepted');
break;
case 3:
$rep['CANCEL'] = '';
case 2:
$rep['STATUS'] = $style->notice(false, 'Unconfirmed');
$rep['RESEND'] = '';
break;
}
if($_POST && !$passthrough) {
if(isset($_POST['change'])) {
$newemail = $_POST['newemail'];
if(preg_match('/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $newemail)) {
$query = $db->query("SELECT `id` FROM `<PRE>users` WHERE `email` = '{$db->strip($newemail)}' AND `id` != '{$db->strip($id)}'");
if($db->num_rows($query) != 0) {
$main->errors("That email address is already in use!");
} else {
$db->query("UPDATE `<PRE>users` SET `newemail` = '{$db->strip($newemail)}' WHERE `id` = {$db->strip($id)}");
if($email->sendConfirmEmail($id)) {
$main->errors('Confirmation email sent to <code>'.$newemail.'</code>');
} else {
$main->errors('Failed to resend email confirmation.');
}
}
} else {
$main->errors("Email incorrectly formatted.");
}
} elseif(isset($_POST['resend']) && $rep['RESEND'] == '') {
$result = $email->sendConfirmEmail($id);
if($result) {
$main->errors("Confirmation email resent to <code>{$result[1]}</code>");
} else {
$main->errors("Failed to resend confirmation email.");
}
} elseif(isset($_POST['cancel']) && $rep['CANCEL'] == '') {
$db->query("UPDATE `<PRE>users` SET `confirmcode` = NULL, `newemail` = NULL WHERE `id` = {$db->strip($id)}");
$main->errors("Canceled email update.");
}
$this->content(true);
return;
}
$client = $db->client($id);
$rep['EMAIL'] = $client['email'];
$rep['NEWEMAIL'] = $client['newemail']!==null?$client['newemail']:'';
echo $style->replaceVar('tpl/clientchangemail.tpl', $rep);
}
}
13 changes: 12 additions & 1 deletion client/pages/home.php
Expand Up @@ -26,7 +26,18 @@ public function content() { # Displays the page
$array['LASTLOGIN'] = $ldata['message'];
$array['LASTDATE'] = strftime("%m/%d/%Y", $ldata['logtime']);
$array['LASTTIME'] = strftime("%T", $ldata['logtime']);
$array['EMAIL'] = $data['email'];
$estatus = $main->getEmailStatus($_SESSION['cuser']);
$array['EMAIL'] = $estatus==3?$data['newemail']:$data['email'];
$array['ESTATUS'] = '<span style="color:green;">Confirmed</span>';
switch ($estatus) {
case 1:
$array['ESTATUS'] = '<span style="color:green;">Accepted</span>';
break;
case 2:
case 3:
$array['ESTATUS'] = '<span style="color:red;font-weight:bold;">Unconfirmed</span>';
break;
}
$array['ALERTS'] = $db->config('alerts');
$query2 = $db->query("SELECT * FROM `<PRE>user_packs` WHERE `userid` = '{$db->strip($data['id'])}'");
$data3 = $db->fetch_array($query2);
Expand Down
22 changes: 22 additions & 0 deletions includes/class_email.php
Expand Up @@ -125,6 +125,28 @@ public function staff($subject, $content, $array = 0) { # Sends every staff memb
$this->send($data['email'], $subject, $content, $array);
}
}

// Send an email confirmation
public function sendConfirmEmail($id) {
global $db;
$id = (int)$id;
$query = $db->query("SELECT `user`,`email`,`newemail`,`confirmcode` FROM `<PRE>users` WHERE `id` = {$id}");
if($db->num_rows($query) == 0) {
return false;
}
$data = $db->fetch_array($query);
if($data['confirmcode'] === null) {
$data['confirmcode'] = sha1($id.mt_rand(0,99999).$data['user'].microtime().$data['email']);
$db->query("UPDATE `<PRE>users` SET `confirmcode` = '".$data['confirmcode']."' WHERE `id` = {$id}");
}
$to = $data['newemail'] === null ? $data['email'] : $data['newemail'];
$template = $db->emailTemplate('emailconfirm');
$confirm = $db->config('url') . 'client/confirm.php?i=' . $id . "&u=".$data['user']."&c=" . $data['confirmcode'];
if($this->send($to, $template['subject'], $template['content'], array('USER' => $data['user'], 'CONFIRM' => $confirm))) {
return array($data['user'], $to);
}
return false;
}

private function parseEmail($content, $array) { # Retrieves the array and replaces all the email variables with the content
foreach($array as $key => $value) {
Expand Down
21 changes: 21 additions & 0 deletions includes/class_main.php
Expand Up @@ -478,5 +478,26 @@ public function getSubversionRevision($cache = true) {
$this->cache['getSubversionRevision'] = false;
return false;
}

// Make sure you use === with this!
// 0 = Confirmed
// 1 = Accepted (not confirmed but email confirmation is not required)
// 2 = Unconfirmed
// 3 = Unconfirmed New Email
public function getEmailStatus($id) {
global $db;
$emailval = (bool)$db->config('emailval');
$client = $db->client($id);
$status = 0;
if($client['newemail'] !== null) {
$status = 3;
} elseif($emailval && !(bool)$client['emailval']) {
$status = 2;

} elseif(!$emailval && !(bool)$client['emailval']) {
$status = 1;
}
return $status;
}
}
?>
66 changes: 39 additions & 27 deletions includes/class_server.php
Expand Up @@ -231,7 +231,7 @@ public function signup() { // Echos the result of signup for ajax
'{$main->getvar['zip']}',
'{$main->getvar['country']}',
'{$main->getvar['phone']}',
'3')");
'1')");
$db->query("INSERT INTO `<PRE>users_bak` (user, email, password, salt, signup, ip, firstname, lastname, address, city, state, zip, country, phone) VALUES(
'{$main->getvar['username']}',
'{$main->getvar['email']}',
Expand Down Expand Up @@ -283,7 +283,13 @@ public function signup() { // Echos the result of signup for ajax
$array['PASS'] = $main->getvar['password'];
$array['EMAIL'] = $main->getvar['email'];
$array['DOMAIN'] = $main->getvar['fdom'];
$array['CONFIRM'] = $url . "client/confirm.php?u=" . $newusername . "&c=" . $date;
$emailval = (bool)$db->config("emailval");
if($emailval) {
$id = $data['id'];
$code = sha1($id.mt_rand(0,99999).$newusername.microtime().$main->getvar['email']);
$db->query("UPDATE `<PRE>users` SET `confirmcode` = '{$code}' WHERE `id` = '{$id}'");
$array['CONFIRM'] = $url . "client/confirm.php?i={$id}&u={$newusername}&c={$code}";
}

//Get plan email friendly name
$pquery = $db->query("SELECT * FROM `<PRE>packages` WHERE `id` = '{$main->getvar['package']}'");
Expand All @@ -293,8 +299,8 @@ public function signup() { // Echos the result of signup for ajax
$puser = $db->query("SELECT * FROM `<PRE>user_packs` WHERE `userid` = '{$data['id']}'");
$puser2 = $db->fetch_array($puser);
if($pname['admin'] == 0) {
$emaildata = $db->emailTemplate("newacc");
echo "<strong>Your account has been completed!</strong><br />You may now use the client login bar to see your client area or proceed to your control panel. An email has been dispatched to the address on file.";
$emaildata = $db->emailTemplate($emailval?"newaccval":"newacc");
echo "<strong>Your account has been created!</strong><br />You may now use the client login bar to see your client area or proceed to your control panel. An email has been dispatched to the address on file.";
if($type->determineType($main->getvar['package']) == "paid") {
echo " This will apply only when you've made payment.";
$_SESSION['clogged'] = 1;
Expand All @@ -303,9 +309,9 @@ public function signup() { // Echos the result of signup for ajax
$donecorrectly = true;
}
elseif($pname['admin'] == 1) {
if($serverphp->suspend($main->getvar['username'], $type->determineServer($main->getvar['package'])) == true) {
if($serverphp->suspend($main->getvar['username'], $type->determineServer($main->getvar['package']), 'TheHostingTool: Awaiting Admin Validation') == true) {
$db->query("UPDATE `<PRE>user_packs` SET `status` = '3' WHERE `id` = '{$puser2['id']}'");
$emaildata = $db->emailTemplate("newaccadmin");
$emaildata = $db->emailTemplate($emailval?"newaccadminval":"newaccadmin");
$emaildata2 = $db->emailTemplate("adminval");
$email->staff($emaildata2['subject'], $emaildata2['content']);
echo "<strong>Your account is awaiting admin validation!</strong><br />An email has been dispatched to the address on file. You will recieve another email when the admin has overlooked your account.";
Expand Down Expand Up @@ -432,8 +438,8 @@ public function decline($id) { // Deletes a user account from the package ID
$emaildata = $db->emailTemplate("cancelacc");
$array['REASON'] = "Account Declined.";
$email->send($data2['email'], $emaildata['subject'], $emaildata['content'], $array);
$db->query("UPDATE `<PRE>user_packs` SET `status` = '9' WHERE `id` = '{$data['id']}'");
$db->query("UPDATE `<PRE>users` SET `status` = '9' WHERE `id` = '{$db->strip($data['userid'])}'");
$db->query("DELETE FROM `<PRE>user_packs` WHERE `id` = '{$data['id']}'");
$db->query("DELETE FROM `<PRE>users` WHERE `id` = '{$db->strip($data['userid'])}'");
$db->query("INSERT INTO `<PRE>logs` (uid, loguser, logtime, message) VALUES(
'{$db->strip($data['userid'])}',
'{$data2['user']}',
Expand Down Expand Up @@ -562,7 +568,7 @@ public function approve($id) { // Approves a user's account (Admin Validation).
$query = $db->query("SELECT * FROM `<PRE>user_packs` WHERE `id` = '{$db->strip($id)}' AND (`status` = '2' OR `status` = '3' OR `status` = '4')");
$uquery = $db->query("SELECT * FROM `<PRE>users` WHERE `id` = '{$query['userid']}' AND (`status` = '1')");
if($db->num_rows($query) == 0 AND $db->num_rows($uquery) == 0) {
$array['Error'] = "That package doesn't exist or cannot be approved! (Did they confirm their e-mail?)";
$array['Error'] = "That package doesn't exist or cannot be approved!";
$array['User PID'] = $id;
$main->error($array);
return;
Expand Down Expand Up @@ -591,25 +597,31 @@ public function approve($id) { // Approves a user's account (Admin Validation).
}
}

public function confirm($username, $confirm) { // Set's user's account to Active when the unique link is visited.
global $db, $main, $type, $email;
$query = $db->query("SELECT * FROM `<PRE>users` WHERE `user` = '{$username}' AND `signup` = {$confirm} AND `status` = '3'");
// Confirms an email
public function confirm($id, $confirm, $user = null, $force = false) {
global $db, $main, $type;
$id = (int)$db->strip($id);
$confirm = $force?'':$db->strip($confirm);
if($user !== null) {
$user = $db->strip($user);
}
$query = $db->query("SELECT * FROM `<PRE>users` WHERE `id` = '{$id}' ".($force?'':"AND `confirmcode` = '{$confirm}'").($user!==null?" AND `user` = '{$user}'":''));
if($db->num_rows($query) == 0) {
$array['Error'] = "That package doesn't exist or cannot be confirmed!";
$main->error($array);
return false;
}
else {
$data = $db->fetch_array($query);
$date = time();
$db->query("UPDATE `<PRE>users` SET `status` = '1' WHERE `user` = '{$username}'");
$db->query("INSERT INTO `<PRE>logs` (uid, loguser, logtime, message) VALUES(
'{$db->strip($data['userid'])}',
'{$data['user']}',
'{$date}',
'Account/E-mail Confirmed.')");
return true;
}
return false;
}
$data = $db->fetch_array($query);
//$db->query("UPDATE `<PRE>users` SET `status` = '1' WHERE `user` = '{$username}'");
$add = "";
if($data['newemail'] !== null) {
$add = "`email` = '".$db->strip($data['newemail'])."', ";
}
$db->query("UPDATE `<PRE>users` SET {$add}`emailval` = 1, `confirmcode` = NULL, `newemail` = NULL WHERE `id` = {$id}");
$db->query("INSERT INTO `<PRE>logs` (uid, loguser, logtime, message) VALUES(
'{$db->strip($data['userid'])}',
'{$data['user']}',
'".time()."',
'Account/E-mail Confirmed ({$data['email']})')");
return true;
}

public function testConnection($serverId) {
Expand Down

0 comments on commit 20aca16

Please sign in to comment.