diff --git a/public/include/autoloader.inc.php b/public/include/autoloader.inc.php index 281b39c6..a5b21621 100644 --- a/public/include/autoloader.inc.php +++ b/public/include/autoloader.inc.php @@ -22,15 +22,18 @@ require_once(INCLUDE_DIR . '/database.inc.php'); require_once(INCLUDE_DIR . '/smarty.inc.php'); // Load classes that need the above as dependencies require_once(CLASS_DIR . '/base.class.php'); +require_once(CLASS_DIR . '/mail.class.php'); +require_once(CLASS_DIR . '/tokentype.class.php'); +require_once(CLASS_DIR . '/token.class.php'); require_once(CLASS_DIR . '/block.class.php'); require_once(CLASS_DIR . '/setting.class.php'); require_once(CLASS_DIR . '/monitoring.class.php'); require_once(CLASS_DIR . '/user.class.php'); +require_once(CLASS_DIR . '/invitation.class.php'); require_once(CLASS_DIR . '/share.class.php'); require_once(CLASS_DIR . '/worker.class.php'); require_once(CLASS_DIR . '/statistics.class.php'); require_once(CLASS_DIR . '/transaction.class.php'); -require_once(CLASS_DIR . '/mail.class.php'); require_once(CLASS_DIR . '/notification.class.php'); require_once(CLASS_DIR . '/news.class.php'); require_once(INCLUDE_DIR . '/lib/Michelf/Markdown.php'); diff --git a/public/include/classes/base.class.php b/public/include/classes/base.class.php index 6b3c2389..836ee3b3 100644 --- a/public/include/classes/base.class.php +++ b/public/include/classes/base.class.php @@ -15,6 +15,9 @@ class Base { public function setMysql($mysqli) { $this->mysqli = $mysqli; } + public function setMail($mail) { + $this->mail = $mail; + } public function setSmarty($smarty) { $this->smarty = $smarty; } @@ -24,6 +27,12 @@ class Base { public function setConfig($config) { $this->config = $config; } + public function setToken($token) { + $this->token = $token; + } + public function setTokenType($tokentype) { + $this->tokentype = $tokentype; + } public function setErrorMessage($msg) { $this->sError = $msg; } diff --git a/public/include/classes/invitation.class.php b/public/include/classes/invitation.class.php new file mode 100644 index 00000000..822dfef3 --- /dev/null +++ b/public/include/classes/invitation.class.php @@ -0,0 +1,146 @@ +debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE account_id = ?"); + if ($stmt && $stmt->bind_param('i', $account_id) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_all(MYSQLI_ASSOC); + $this->setErrorMessage('Unable to fetch invitiations send from your account'); + $this->debug->append('Failed to fetch invitations from database: ' . $this->mysqli->errro); + return false; + } + + /** + * Count invitations sent by an account_id + * @param account_id integer Account ID + * @return mixes Integer on success, boolean on failure + **/ + public function getCountInvitations($account_id) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("SELECT count(id) AS total FROM $this->table WHERE account_id = ?"); + if ($stmt && $stmt->bind_param('i', $account_id) && $stmt->execute() && $stmt->bind_result($total) && $stmt->fetch()) + return $total; + $this->setErrorMessage('Unable to fetch invitiations send from your account'); + $this->debug->append('Failed to fetch invitations from database: ' . $this->mysqli->errro); + return false; + } + + /** + * Get a specific invitation by email address + * Used to ensure no invitation was already sent + * @param strEmail string Email address to check for + * @return bool boolean true of ralse + **/ + public function getByEmail($strEmail) { + $this->debug->append("STA " . __METHOD__, 4); + return $this->getSingle($strEmail, 'id', 'email', 's'); + } + + /** + * Get a specific token by token ID + * Used to match an invitation against a token + * @param token_id integer Token ID stored in invitation + * @return data mixed Invitation ID on success, false on error + **/ + public function getByTokenId($token_id) { + $this->debug->append("STA " . __METHOD__, 4); + return $this->getSingle($token_id, 'id', 'token_id'); + } + + /** + * Set an invitation as activated by the invitee + * @param token_id integer Token to activate + * @return bool boolean true or false + **/ + public function setActivated($token_id) { + if (!$iInvitationId = $this->getByTokenId($token_id)) { + $this->setErrorMessage('Unable to convert token ID to invitation ID'); + return false; + } + $field = array('name' => 'is_activated', 'type' => 'i', 'value' => 1); + return $this->updateSingle($iInvitationId, $field); + } + + /** + * Insert a new invitation to the database + * @param account_id integer Account ID to bind the invitation to + * @param email string Email address the invite was sent to + * @param token_id integer Token ID used during invitation + * @return bool boolean True of false + **/ + public function createInvitation($account_id, $email, $token_id) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("INSERT INTO $this->table ( account_id, email, token_id ) VALUES ( ?, ?, ?)"); + if ($stmt && $stmt->bind_param('isi', $account_id, $email, $token_id) && $stmt->execute()) + return true; + return false; + } + /** + * Send an invitation out to a user + * Uses the mail class to send mails + * @param account_id integer Sending account ID + * @param aData array Data array including mail information + * @return bool boolean True or false + **/ + public function sendInvitation($account_id, $aData) { + $this->debug->append("STA " . __METHOD__, 4); + // Check data input + if (empty($aData['email']) || !filter_var($aData['email'], FILTER_VALIDATE_EMAIL)) { + $this->setErrorMessage( 'Invalid e-mail address' ); + return false; + } + if (preg_match('/[^a-z_\.\!\?\-0-9 ]/i', $aData['message'])) { + $this->setErrorMessage('Message may only contain alphanumeric characters'); + return false; + } + // Ensure this invitation does not exist yet nor do we have an account with that email + if ($this->user->getEmail($aData['email'])) { + $this->setErrorMessage('This email is already registered as an account'); + return false; + } + if ($this->getByEmail($aData['email'])) { + $this->setErrorMessage('A pending invitation for this address already exists'); + return false; + } + if (!$aData['token'] = $this->token->createToken('invitation', $account_id)) { + $this->setErrorMessage('Unable to generate invitation token: ' . $this->token->getError()); + return false; + } + $aData['username'] = $this->user->getUserName($account_id); + $aData['subject'] = 'Pending Invitation'; + if ($this->mail->sendMail('invitations/body', $aData)) { + $aToken = $this->token->getToken($aData['token']); + if (!$this->createInvitation($account_id, $aData['email'], $aToken['id'])) { + $this->setErrorMessage('Unable to create invitation record'); + return false; + } + return true; + } else { + $this->setErrorMessage('Unable to send email to recipient'); + } + $this->setErrorMessage('Unable to send invitation'); + return false; + } +} + +// Instantiate class +$invitation = new invitation(); +$invitation->setDebug($debug); +$invitation->setMysql($mysqli); +$invitation->setMail($mail); +$invitation->setUser($user); +$invitation->setToken($oToken); +$invitation->setConfig($config); + +?> diff --git a/public/include/classes/token.class.php b/public/include/classes/token.class.php new file mode 100644 index 00000000..c65da13c --- /dev/null +++ b/public/include/classes/token.class.php @@ -0,0 +1,60 @@ +mysqli->prepare("SELECT * FROM $this->table WHERE token = ? LIMIT 1"); + if ($stmt && $stmt->bind_param('s', $strToken) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_assoc(); + return false; + } + + /** + * Insert a new token + * @param name string Name of the variable + * @param value string Variable value + * @return mixed Token string on success, false on failure + **/ + public function createToken($strType, $account_id=NULL) { + $strToken = hash('sha256', $account_id.$strType.microtime()); + if (!$iToken_id = $this->tokentype->getTypeId($strType)) { + $this->setErrorMessage('Invalid token type: ' . $strType); + return false; + } + $stmt = $this->mysqli->prepare(" + INSERT INTO $this->table (token, type, account_id) + VALUES (?, ?, ?) + "); + if ($stmt && $stmt->bind_param('sii', $strToken, $iToken_id, $account_id) && $stmt->execute()) + return $strToken; + $this->setErrorMessage('Unable to create new token'); + $this->debug->append('Failed to create new token in database: ' . $this->mysqli->error); + return false; + } + + /** + * Delete a used token + * @param token string Token name + * @return bool + **/ + public function deleteToken($token) { + $stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE token = ? LIMIT 1"); + if ($stmt && $stmt->bind_param('s', $token) && $stmt->execute()) + return true; + return false; + } +} + +$oToken = new Token(); +$oToken->setDebug($debug); +$oToken->setMysql($mysqli); +$oToken->setTokenType($tokentype); diff --git a/public/include/classes/tokentype.class.php b/public/include/classes/tokentype.class.php new file mode 100644 index 00000000..d33356cb --- /dev/null +++ b/public/include/classes/tokentype.class.php @@ -0,0 +1,21 @@ +getSingle($strName, 'id', 'name', 's'); + } +} + +$tokentype = new Token_Type(); +$tokentype->setDebug($debug); +$tokentype->setMysql($mysqli); diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index bdd87d1e..9d448a40 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -9,7 +9,6 @@ class User { private $userID = false; private $table = 'accounts'; private $user = array(); - private $tableAccountBalance = 'accountBalance'; public function __construct($debug, $mysqli, $salt, $config) { $this->debug = $debug; @@ -20,6 +19,12 @@ class User { } // get and set methods + public function setMail($mail) { + $this->mail = $mail; + } + public function setToken($token) { + $this->token= $token; + } private function setErrorMessage($msg) { $this->sError = $msg; } @@ -44,9 +49,6 @@ class User { public function getUserLocked($id) { return $this->getSingle($id, 'is_locked', 'id'); } - public function getUserToken($id) { - return $this->getSingle($id, 'token', 'id'); - } public function getUserIp($id) { return $this->getSingle($id, 'loggedIp', 'id'); } @@ -56,9 +58,6 @@ class User { public function getUserFailed($id) { return $this->getSingle($id, 'failed_logins', 'id'); } - public function getIdFromToken($token) { - return $this->getSingle($token, 'id', 'token', 's'); - } public function isLocked($id) { return $this->getUserLocked($id); } @@ -73,10 +72,6 @@ class User { $field = array('name' => 'is_admin', 'type' => 'i', 'value' => !$this->isAdmin($id)); return $this->updateSingle($id, $field); } - public function setUserToken($id) { - $field = array('name' => 'token', 'type' => 's', 'value' => setHash($id.time())); - return $this->updateSingle($id, $field); - } public function setUserFailed($id, $value) { $field = array( 'name' => 'failed_logins', 'type' => 'i', 'value' => $value); return $this->updateSingle($id, $field); @@ -449,7 +444,7 @@ class User { * @param email2 string Email confirmation * @return bool **/ - public function register($username, $password1, $password2, $pin, $email1='', $email2='') { + public function register($username, $password1, $password2, $pin, $email1='', $email2='', $strToken='') { $this->debug->append("STA " . __METHOD__, 4); if (strlen($username > 40)) { $this->setErrorMessage('Username exceeding character limit'); @@ -483,15 +478,33 @@ class User { $this->setErrorMessage( 'Invalid PIN' ); return false; } + if (isset($strToken) && !empty($strToken)) { + $aToken = $this->token->getToken($strToken); + // Circle dependency, so we create our own object here + $invitation = new Invitation(); + $invitation->setMysql($this->mysqli); + $invitation->setDebug($this->debug); + $invitation->setUser($this); + $invitation->setConfig($this->config); + if (!$invitation->setActivated($aToken['id'])) { + $this->setErrorMessage('Unable to activate your invitation'); + return false; + } + if (!$this->token->deleteToken($strToken)) { + $this->setErrorMessage('Unable to remove used token'); + return false; + } + } if ($this->mysqli->query("SELECT id FROM $this->table LIMIT 1")->num_rows > 0) { + $this->config['accounts']['confirm_email']['enabled'] ? $is_locked = 1 : $is_locked = 0; $stmt = $this->mysqli->prepare(" - INSERT INTO $this->table (username, pass, email, pin, api_key) - VALUES (?, ?, ?, ?, ?) + INSERT INTO $this->table (username, pass, email, pin, api_key, is_locked) + VALUES (?, ?, ?, ?, ?, ?) "); } else { $stmt = $this->mysqli->prepare(" - INSERT INTO $this->table (username, pass, email, pin, api_key, is_admin) - VALUES (?, ?, ?, ?, ?, 1) + INSERT INTO $this->table (username, pass, email, pin, api_key, is_admin, is_locked) + VALUES (?, ?, ?, ?, ?, 1, ?) "); } @@ -501,14 +514,31 @@ class User { $apikey_hash = $this->getHash($username); $username_clean = strip_tags($username); - if ($this->checkStmt($stmt) && $stmt->bind_param('sssss', $username_clean, $password_hash, $email1, $pin_hash, $apikey_hash)) { - if (!$stmt->execute()) { - $this->setErrorMessage( 'Unable to register' ); - if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username or email already registered' ); - return false; + if ($this->checkStmt($stmt) && $stmt->bind_param('sssssi', $username_clean, $password_hash, $email1, $pin_hash, $apikey_hash, $is_locked) && $stmt->execute()) { + if ($this->config['accounts']['confirm_email']['enabled']) { + if ($token = $this->token->createToken('confirm_email', $stmt->insert_id)) { + $aData['username'] = $username_clean; + $aData['token'] = $token; + $aData['email'] = $email1; + $aData['subject'] = 'E-Mail verification'; + if (!$this->mail->sendMail('register/confirm_email', $aData)) { + $this->setErrorMessage('Unable to request email confirmation'); + return false; + } + return true; + } else { + $this->setErrorMessage('Failed to create confirmation token'); + $this->debug->append('Unable to create confirm_email token: ' . $this->token->getError()); + return false; + } + } else { + return true; } - $stmt->close(); - return true; + } else { + $this->setErrorMessage( 'Unable to register' ); + $this->debug->append('Failed to insert user into DB: ' . $this->mysqli->error); + if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username or email already registered' ); + return false; } return false; } @@ -520,9 +550,9 @@ class User { * @param new2 string New password verification * @return bool **/ - public function useToken($token, $new1, $new2) { + public function resetPassword($token, $new1, $new2) { $this->debug->append("STA " . __METHOD__, 4); - if ($id = $this->getIdFromToken($token)) { + if ($aToken = $this->token->getToken($token)) { if ($new1 !== $new2) { $this->setErrorMessage( 'New passwords do not match' ); return false; @@ -532,54 +562,46 @@ class User { return false; } $new_hash = $this->getHash($new1); - $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ?, token = NULL WHERE id = ? AND token = ?"); - if ($this->checkStmt($stmt) && $stmt->bind_param('sis', $new_hash, $id, $token) && $stmt->execute() && $stmt->affected_rows === 1) { - return true; + $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE id = ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param('si', $new_hash, $aToken['account_id']) && $stmt->execute() && $stmt->affected_rows === 1) { + if ($this->token->deleteToken($aToken['token'])) { + return true; + } else { + $this->setErrorMessage('Unable to invalidate used token'); + } + } else { + $this->setErrorMessage('Unable to set new password'); } } else { - $this->setErrorMessage("Unable find user for your token"); - return false; + $this->setErrorMessage('Invalid token'); } + $this->debug->append('Failed to update password:' . $this->mysqli->error); return false; } /** * Reset a password by sending a password reset mail * @param username string Username to reset password for - * @param smarty object Smarty object for mail templating * @return bool **/ - public function resetPassword($username, $smarty) { + public function initResetPassword($username) { $this->debug->append("STA " . __METHOD__, 4); // Fetch the users mail address if (empty($username)) { $this->serErrorMessage("Username must not be empty"); return false; } - if (!$email = $this->getUserEmail($username)) { + if (!$aData['email'] = $this->getUserEmail($username)) { $this->setErrorMessage("Unable to find a mail address for user $username"); return false; } - if (!$this->setUserToken($this->getUserId($username))) { - $this->setErrorMessage("Unable to setup token for password reset"); + if (!$aData['token'] = $this->token->createToken('password_reset', $this->getUserId($username))) { + $this->setErrorMessage('Unable to setup token for password reset'); return false; } - // Send password reset link - if (!$token = $this->getUserToken($this->getUserId($username))) { - $this->setErrorMessage("Unable fetch token for password reset"); - return false; - } - $smarty->assign('TOKEN', $token); - $smarty->assign('USERNAME', $username); - $smarty->assign('SUBJECT', 'Password Reset Request'); - $smarty->assign('WEBSITENAME', $this->config['website']['name']); - $headers = 'From: Website Administration <' . $this->config['website']['email'] . ">\n"; - $headers .= "MIME-Version: 1.0\n"; - $headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n"; - if (mail($email, - $smarty->fetch('templates/mail/subject.tpl'), - $smarty->fetch('templates/mail/body.tpl'), - $headers)) { + $aData['username'] = $username; + $aData['subject'] = 'Password Reset Request'; + if ($this->mail->sendMail('password/reset', $aData)) { return true; } else { $this->setErrorMessage("Unable to send mail to your address"); @@ -609,3 +631,5 @@ class User { // Make our class available automatically $user = new User($debug, $mysqli, SALT, $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 25667017..e66197fe 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -121,6 +121,33 @@ $config['website']['theme'] = 'mmcFE'; $config['website']['mobile'] = true; $config['website']['mobile_theme'] = 'mobile'; +/** + * Account specific settings + * + * Explanation + * You can change some defaults on how accounts are created or registered + * By default, all newly created accounts will require an email verificaiton. + * Only after acitivating an account the user will be able to login + * + * Invitations will allow your users to invite new members to join the pool. + * After sending a mail to the invited user, they can register using the token + * created. Invitations can be enabled and disabled through the admin panel. + * Sent invitations are listed on the account invitations page. + * + * You can limit the number of registrations send per account via configuration + * variable. + * + * Options: + * confirm_email : Send confirmation mail to user after registration + * count : Maximum invitations a user is able to send + * + * Defaults: + * confirm_email : true + * count : 5 + **/ +$config['accounts']['confirm_email']['enabled'] = true; +$config['accounts']['invitations']['count'] = 5; + /** * Some basic access restrictions on some pages * diff --git a/public/include/pages/account/confirm.inc.php b/public/include/pages/account/confirm.inc.php new file mode 100644 index 00000000..3611c5de --- /dev/null +++ b/public/include/pages/account/confirm.inc.php @@ -0,0 +1,17 @@ + 'Missing token', 'TYPE' => 'errormsg'); +} else if (!$aToken = $oToken->getToken($_GET['token'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to activate your account. Invalid token', 'TYPE' => 'errormsg'); +} else { + $user->changeLocked($aToken['account_id']); + $oToken->deleteToken($aToken['token']); + $_SESSION['POPUP'][] = array('CONTENT' => 'Account activated. Please login.'); +} +$smarty->assign('CONTENT', 'default.tpl'); +?> diff --git a/public/include/pages/account/invitations.inc.php b/public/include/pages/account/invitations.inc.php new file mode 100644 index 00000000..b12e2d4e --- /dev/null +++ b/public/include/pages/account/invitations.inc.php @@ -0,0 +1,25 @@ +isAuthenticated()) { + if (!$setting->getValue('disable_invitations')) { + if ($invitation->getCountInvitations($_SESSION['USERDATA']['id']) >= $config['accounts']['invitations']['count']) { + $_SESSION['POPUP'][] = array('CONTENT' => 'You have exceeded the allowed invitations of ' . $config['accounts']['invitations']['count'], 'TYPE' => 'errormsg'); + } else if (isset($_POST['do']) && $_POST['do'] == 'sendInvitation') { + if ($invitation->sendInvitation($_SESSION['USERDATA']['id'], $_POST['data'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Invitation sent'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to send invitation to recipient: ' . $invitation->getError(), 'TYPE' => 'errormsg'); + } + } + $aInvitations = $invitation->getInvitations($_SESSION['USERDATA']['id']); + $smarty->assign('INVITATIONS', $aInvitations); + } else { + $aInvitations = array(); + $_SESSION['POPUP'][] = array('CONTENT' => 'Invitations are disabled', 'TYPE' => 'errormsg'); + } +} +$smarty->assign('CONTENT', 'default.tpl'); +?> diff --git a/public/include/pages/account/reset_failed.inc.php b/public/include/pages/account/reset_failed.inc.php index bce9b418..39541dc2 100644 --- a/public/include/pages/account/reset_failed.inc.php +++ b/public/include/pages/account/reset_failed.inc.php @@ -8,5 +8,6 @@ if ($user->isAuthenticated()) { $user->setUserFailed($_SESSION['USERDATA']['id'], 0); header("Location: " . $_SERVER['HTTP_REFERER']); } - +// Somehow we still need to load this empty template +$smarty->assign("CONTENT", "../../global/empty.tpl"); ?> diff --git a/public/include/pages/admin/settings.inc.php b/public/include/pages/admin/settings.inc.php index dab1f096..9bc7ef3b 100644 --- a/public/include/pages/admin/settings.inc.php +++ b/public/include/pages/admin/settings.inc.php @@ -19,6 +19,7 @@ if (@$_REQUEST['do'] == 'save' && !empty($_REQUEST['data'])) { // Fetch settings to propagate to template $smarty->assign("MAINTENANCE", $setting->getValue('maintenance')); $smarty->assign("LOCKREGISTRATION", $setting->getValue('lock_registration')); +$smarty->assign("DISABLEINVITATIONS", $setting->getValue('disable_invitations')); // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); diff --git a/public/include/pages/password/change.inc.php b/public/include/pages/password/change.inc.php index 8b5f4064..919632bd 100644 --- a/public/include/pages/password/change.inc.php +++ b/public/include/pages/password/change.inc.php @@ -4,14 +4,14 @@ if (!defined('SECURITY')) die('Hacking attempt'); -if ($_POST['do'] == 'useToken') { - if ($user->useToken($_POST['token'], $_POST['newPassword'], $_POST['newPassword2'])) { +if (isset($_POST['do']) && $_POST['do'] == 'resetPassword') { + if ($user->resetPassword($_POST['token'], $_POST['newPassword'], $_POST['newPassword2'])) { $_SESSION['POPUP'][] = array('CONTENT' => 'Password reset complete! Please login.'); } else { $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); } } - // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); + ?> diff --git a/public/include/pages/password/reset.inc.php b/public/include/pages/password/reset.inc.php index 796f5811..f7334fe8 100644 --- a/public/include/pages/password/reset.inc.php +++ b/public/include/pages/password/reset.inc.php @@ -5,7 +5,7 @@ if (!defined('SECURITY')) die('Hacking attempt'); // Process password reset request -if ($user->resetPassword($_POST['username'], $smarty)) { +if ($user->initResetPassword($_POST['username'], $smarty)) { $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mail account to finish your password reset'); } else { $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); diff --git a/public/include/pages/register.inc.php b/public/include/pages/register.inc.php index d47c67ed..01b71b18 100644 --- a/public/include/pages/register.inc.php +++ b/public/include/pages/register.inc.php @@ -3,9 +3,12 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); -if ($setting->getValue('lock_registration')) { +if ($setting->getValue('lock_registration') && $setting->getValue('disable_invitations')) { $_SESSION['POPUP'][] = array('CONTENT' => 'Account registration is currently disabled. Please try again later.', 'TYPE' => 'errormsg'); $smarty->assign("CONTENT", "disabled.tpl"); +} else if ($setting->getValue('lock_registration') && !$setting->getValue('disable_invitations') && !isset($_GET['token'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Only invited users are allowed to register.', 'TYPE' => 'errormsg'); + $smarty->assign("CONTENT", "disabled.tpl"); } else { if ($config['recaptcha']['enabled']) { require_once(INCLUDE_DIR . '/lib/recaptchalib.php'); diff --git a/public/include/pages/register/register.inc.php b/public/include/pages/register/register.inc.php index 01a27e10..ca165e3b 100644 --- a/public/include/pages/register/register.inc.php +++ b/public/include/pages/register/register.inc.php @@ -13,33 +13,37 @@ if ($config['recaptcha']['enabled']) { ); } -// Check if recaptcha is enabled, process form data if valid -if($config['recaptcha']['enabled'] && $_POST["recaptcha_response_field"] && $_POST["recaptcha_response_field"]!=''){ - if ($rsp->is_valid) { - $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'])); - if ($setting->getValue('lock_registration')) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account registration is currently disabled. Please try again later.', 'TYPE' => 'errormsg'); - } else if ($user->register($_POST['username'], $_POST['password1'], $_POST['password2'], $_POST['pin'], $_POST['email1'], $_POST['email2']) && !$setting->getValue('lock_registration')) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); +if ($setting->getValue('disable_invitations') && $setting->getValue('lock_registration')) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Account registration is currently disabled. Please try again later.', 'TYPE' => 'errormsg'); +} else if ($setting->getValue('lock_registration') && !$setting->getValue('disable_invitations') && !isset($_POST['token'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Only invited users are allowed to register.', 'TYPE' => 'errormsg'); +} else { + // Check if recaptcha is enabled, process form data if valid + if($config['recaptcha']['enabled'] && $_POST["recaptcha_response_field"] && $_POST["recaptcha_response_field"]!=''){ + if ($rsp->is_valid) { + $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'])); + isset($_POST['token']) ? $token = $_POST['token'] : $token = ''; + if ($user->register($_POST['username'], $_POST['password1'], $_POST['password2'], $_POST['pin'], $_POST['email1'], $_POST['email2'], $token)) { + $config['accounts']['confirm_email']['enabled'] ? $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mailbox to activate this account') : $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to create account: ' . $user->getError(), 'TYPE' => 'errormsg'); + } + } else { + $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'], $rsp->error)); + $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid Captcha, please try again. (' . $rsp->error . ')', 'TYPE' => 'errormsg'); + } + // Empty captcha + } else if ($config['recaptcha']['enabled']) { + $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'], $rsp->error)); + $_SESSION['POPUP'][] = array('CONTENT' => 'Empty Captcha, please try again.', 'TYPE' => 'errormsg'); + // Captcha disabled + } else { + isset($_POST['token']) ? $token = $_POST['token'] : $token = ''; + if ($user->register($_POST['username'], $_POST['password1'], $_POST['password2'], $_POST['pin'], $_POST['email1'], $_POST['email2'], $token)) { + $config['accounts']['confirm_email']['enabled'] ? $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mailbox to activate this account') : $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to create account: ' . $user->getError(), 'TYPE' => 'errormsg'); } - } else { - $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'], $rsp->error)); - $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid Captcha, please try again. (' . $rsp->error . ')', 'TYPE' => 'errormsg'); - } -// Empty captcha -} else if ($config['recaptcha']['enabled']) { - $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'], $rsp->error)); - $_SESSION['POPUP'][] = array('CONTENT' => 'Empty Captcha, please try again.', 'TYPE' => 'errormsg'); -// Captcha disabled -} else { - if ($setting->getValue('lock_registration')) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account registration is currently disabled. Please try again later.', 'TYPE' => 'errormsg'); - } else if ($user->register($_POST['username'], $_POST['password1'], $_POST['password2'], $_POST['pin'], $_POST['email1'], $_POST['email2']) && !$setting->getValue('lock_registration')) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to create account: ' . $user->getError(), 'TYPE' => 'errormsg'); } } diff --git a/public/include/smarty.inc.php b/public/include/smarty.inc.php index f95180d7..dfd1f4f6 100644 --- a/public/include/smarty.inc.php +++ b/public/include/smarty.inc.php @@ -18,6 +18,7 @@ $smarty = new Smarty; $debug->append('Define Smarty Paths', 3); $smarty->template_dir = BASEPATH . 'templates/' . THEME . '/'; $smarty->compile_dir = BASEPATH . 'templates/compile/'; +$smarty_cache_key = md5(serialize($_REQUEST) . serialize(@$_SESSION['USERDATA']['id'])); // Optional smarty caching, check Smarty documentation for details if ($config['smarty']['cache']) { @@ -26,6 +27,5 @@ if ($config['smarty']['cache']) { $smarty->cache_lifetime = $config['smarty']['cache_lifetime']; $smarty->cache_dir = BASEPATH . "templates/cache"; $smarty->use_sub_dirs = true; - $smarty_cache_key = md5(serialize($_REQUEST) . serialize(@$_SESSION['USERDATA']['id'])); } ?> diff --git a/public/include/smarty_globals.inc.php b/public/include/smarty_globals.inc.php index a3a47e66..5f6ca959 100644 --- a/public/include/smarty_globals.inc.php +++ b/public/include/smarty_globals.inc.php @@ -48,6 +48,8 @@ $aGlobal = array( 'chaininfo' => $config['chaininfo'], 'config' => array( 'website' => array( 'title' => $config['website']['title'], 'acl' => $config['website']['acl'] ), + 'accounts' => $config['accounts'], + 'disable_invitations' => $setting->getValue('disable_invitations'), 'price' => array( 'currency' => $config['price']['currency'] ), 'targetdiff' => $config['difficulty'], 'currency' => $config['currency'], diff --git a/public/templates/mail/invitations/body.tpl b/public/templates/mail/invitations/body.tpl new file mode 100644 index 00000000..353b82e0 --- /dev/null +++ b/public/templates/mail/invitations/body.tpl @@ -0,0 +1,11 @@ + + +

Hello valued miner,


+

{$DATA.username} invited you to participate on this pool: +

http://{$smarty.server.SERVER_NAME}{$smarty.server.PHP_SELF}?page=register&token={$DATA.token}

+{if $DATA.message}

Personal message:

{$DATA.message}

{/if} +

+

Cheers,

+

Website Administration

+ + diff --git a/public/templates/mail/body.tpl b/public/templates/mail/password/reset.tpl similarity index 80% rename from public/templates/mail/body.tpl rename to public/templates/mail/password/reset.tpl index a80b579a..fe488dca 100644 --- a/public/templates/mail/body.tpl +++ b/public/templates/mail/password/reset.tpl @@ -1,8 +1,8 @@ -

Hello {$USERNAME},


+

Hello {$DATA.username},


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

-

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

+

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

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

Cheers,

Website Administration

diff --git a/public/templates/mail/register/confirm_email.tpl b/public/templates/mail/register/confirm_email.tpl new file mode 100644 index 00000000..67ea72c5 --- /dev/null +++ b/public/templates/mail/register/confirm_email.tpl @@ -0,0 +1,10 @@ + + +

Hello {$DATA.username},


+

You have create a new account. In order to complete the registration process please follow this link:

+

http://{$smarty.server.SERVER_NAME}{$smarty.server.PHP_SELF}?page=account&action=confirm&token={$DATA.token}

+

+

Cheers,

+

Website Administration

+ + diff --git a/public/templates/mmcFE/account/confirm/default.tpl b/public/templates/mmcFE/account/confirm/default.tpl new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/public/templates/mmcFE/account/confirm/default.tpl @@ -0,0 +1 @@ + diff --git a/public/templates/mmcFE/account/invitations/default.tpl b/public/templates/mmcFE/account/invitations/default.tpl new file mode 100644 index 00000000..b8d6743a --- /dev/null +++ b/public/templates/mmcFE/account/invitations/default.tpl @@ -0,0 +1,43 @@ +{include file="global/block_header.tpl" ALIGN="left" BLOCK_HEADER="Invitations"} +
+ + + + + + + + + + + + + + + +
E-Mail
Message
+ +
+
+{include file="global/block_footer.tpl"} + +{include file="global/block_header.tpl" ALIGN="right" BLOCK_HEADER="Past Invitations"} + + + + + + + + + +{section name=invite loop=$INVITATIONS} + + + + + +{/section} + +
E-MailSentActivated
{$INVITATIONS[invite].email}{$INVITATIONS[invite].time|date_format:"%d/%m/%Y %H:%M:%S"}
+{include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/admin/settings/default.tpl b/public/templates/mmcFE/admin/settings/default.tpl index a2ffbd5b..154c39d1 100644 --- a/public/templates/mmcFE/admin/settings/default.tpl +++ b/public/templates/mmcFE/admin/settings/default.tpl @@ -30,6 +30,16 @@ + + Disable Invitations + + + + + diff --git a/public/templates/mmcFE/global/empty.tpl b/public/templates/mmcFE/global/empty.tpl new file mode 100644 index 00000000..e69de29b diff --git a/public/templates/mmcFE/global/navigation.tpl b/public/templates/mmcFE/global/navigation.tpl index 42c89e9a..79d36af8 100644 --- a/public/templates/mmcFE/global/navigation.tpl +++ b/public/templates/mmcFE/global/navigation.tpl @@ -7,6 +7,7 @@
  • My Workers
  • Transactions
  • Notifications
  • + {if !$GLOBAL.config.disable_invitations}
  • Invitations
  • {/if} {/if} diff --git a/public/templates/mmcFE/password/change/default.tpl b/public/templates/mmcFE/password/change/default.tpl index 0d254677..ae3aab55 100644 --- a/public/templates/mmcFE/password/change/default.tpl +++ b/public/templates/mmcFE/password/change/default.tpl @@ -3,7 +3,7 @@ - + diff --git a/public/templates/mmcFE/register/default.tpl b/public/templates/mmcFE/register/default.tpl index 3316dfa9..7253a243 100644 --- a/public/templates/mmcFE/register/default.tpl +++ b/public/templates/mmcFE/register/default.tpl @@ -1,6 +1,9 @@ {include file="global/block_header.tpl" BLOCK_HEADER="Join our pool" BLOCK_STYLE="clear:none;"} +{if $smarty.request.token|default:""} + +{/if}
    New Password:
    New Password Repeat:
    diff --git a/sql/004_tokens_invitations.sql b/sql/004_tokens_invitations.sql new file mode 100644 index 00000000..3ed5a8dc --- /dev/null +++ b/sql/004_tokens_invitations.sql @@ -0,0 +1,68 @@ +SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + + +CREATE TABLE IF NOT EXISTS `tokens` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `account_id` int(11) NOT NULL, + `token` varchar(65) NOT NULL, + `type` tinyint(4) NOT NULL, + `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `token` (`token`), + KEY `account_id` (`account_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +/*!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 */; +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + + +CREATE TABLE IF NOT EXISTS `token_types` ( + `id` tinyint(4) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(25) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ; + +INSERT INTO `token_types` (`id`, `name`) VALUES +(1, 'password_reset'), +(2, 'confirm_email'), +(3, 'invitation'); + +/*!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 */; +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + + +CREATE TABLE IF NOT EXISTS `invitations` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `account_id` int(11) unsigned NOT NULL, + `email` varchar(50) CHARACTER SET utf8 NOT NULL, + `token_id` int(11) NOT NULL, + `is_activated` tinyint(1) NOT NULL DEFAULT '0', + `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +/*!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 */;