Allow resetting password through email.

This also adopts the use of an Alert template for error and success messages.
This commit is contained in:
2016-09-02 11:17:10 +02:00
parent 3587447cc0
commit 7487068171
13 changed files with 456 additions and 20 deletions

View File

@@ -44,6 +44,31 @@ class Authentication
return empty($res) ? false : $res;
}
public static function setResetKey($id_user)
{
return Registry::get('db')->query('
UPDATE users
SET reset_key = {string:key}
WHERE id_user = {int:id}',
[
'id' => $id_user,
'key' => self::newActivationKey(),
]);
}
public static function checkResetKey($id_user, $reset_key)
{
$key = Registry::get('db')->queryValue('
SELECT reset_key
FROM users
WHERE id_user = {int:id}',
[
'id' => $id_user,
]);
return $key == $reset_key;
}
/**
* Verifies whether the user is currently logged in.
*/
@@ -62,6 +87,18 @@ class Authentication
return isset($_SESSION['user_id']) && self::checkExists($_SESSION['user_id']);
}
/**
* Generates a new activation key.
*/
public static function newActivationKey()
{
$alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$string = '';
for ($i = 0; $i < 16; $i++)
$string .= $alpha[mt_rand(0, strlen($alpha) - 1)];
return $string;
}
/**
* Checks a password for a given username against the database.
*/

View File

@@ -21,6 +21,7 @@ class Dispatcher
'managetags' => 'ManageTags',
'manageusers' => 'ManageUsers',
'people' => 'ViewPeople',
'resetpassword' => 'ResetPassword',
'suggest' => 'ProvideAutoSuggest',
'timeline' => 'ViewTimeline',
'uploadmedia' => 'UploadMedia',
@@ -77,6 +78,11 @@ class Dispatcher
else
self::trigger403();
}
catch (UserFacingException $e)
{
$debug_info = ErrorHandler::getDebugInfo($e->getTrace());
ErrorHandler::display($e->getMessage(), $debug_info, false);
}
catch (Exception $e)
{
ErrorHandler::handleError(E_USER_ERROR, 'Unspecified exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
@@ -93,7 +99,7 @@ class Dispatcher
public static function kickGuest()
{
$form = new LogInForm('Log in');
$form->setErrorMessage('Admin access required. Please log in.');
$form->adopt(new Alert('', 'You need to be logged in to view this page.', 'error'));
$form->setRedirectUrl($_SERVER['REQUEST_URI']);
$page = new MainTemplate('Login required');

100
models/Email.php Normal file
View File

@@ -0,0 +1,100 @@
<?php
/*****************************************************************************
* Email.php
* Contains key class Email.
*
* Kabuki CMS (C) 2013-2016, Aaron van Geffen
*****************************************************************************/
class Email
{
public static function send($address, $addressee, $subject, $body, $headers = '')
{
// Set a boundary.
$boundary = uniqid('sr');
if (empty($headers))
$headers .= "From: HashRU Pics <no-reply@pics.hashru.nl>\r\n";
// Set up headers.
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: multipart/alternative;boundary=$boundary\r\n";
// Start the message with a plaintext version of the mail.
$message = "This is a MIME encoded message.";
$message .= "\r\n\r\n--$boundary\r\n";
$message .= "Content-type: text/plain;charset=utf-8\r\n\r\n";
$message .= self::wrapLines($body);
// Autolink URLs and wrap lines.
$html_body = preg_replace('~\b(?!")https?://[^"\s]+~', '<a href="$0">$0</a>', $body);
$html_body = preg_replace('~(\s+)(www\..+\.[^"\s]+?)(\.|\s+)~', '$1<a href="https://$2">$2</a>$3', $html_body);
$html_body = preg_replace('~={10,}~', '<hr>', $html_body);
$html_body = self::wrapLines(str_replace("\r", "<br>\r", $html_body), 80, "\r\n");
// Then, more excitingly, add an HTML version!
$message .= "\r\n\r\n--" . $boundary . "\r\n";
$message .= "Content-type: text/html;charset=utf-8\r\n\r\n";
$message .= "<html>\r\n<head><style type=\"text/css\">\r\nbody { font: 13px Helvetica, Arial, sans-serif; }\r\n</style></head>\r\n<body><p>" .
$html_body . "</p></body>\r\n</html>";
// End off with a final boundary.
$message .= "\r\n\r\n--" . $boundary . "--";
if (DEBUG)
return file_put_contents(BASEDIR . '/mail_dumps.txt', "To: \"$addressee\" <$address>\r\n$headers\r\nSubject: $subject\r\n" . self::wrapLines($message), FILE_APPEND);
else
return mail("\"$addressee\" <$address>", $subject, $message, $headers, '-fbounces@pics.hashru.nl');
}
public static function wrapLines($body, $maxlength = 80, $break = "\r\n")
{
$lines = explode("\n", $body);
$wrapped = "";
foreach ($lines as $line)
$wrapped .= wordwrap($line, $maxlength, $break) . $break;
return $wrapped;
}
private static function parseTemplate($template, $replacements)
{
$replacement_keys = array_map(function($el) { return "%$el%"; }, array_keys($replacements));
$subject = str_replace($replacement_keys, array_values($replacements), $template['subject']);
$body = str_replace($replacement_keys, array_values($replacements), $template['body']);
return [$subject, $body];
}
public static function resetMail($id_user)
{
$row = Registry::get('db')->queryAssoc('
SELECT first_name, surname, emailaddress, reset_key
FROM users
WHERE id_user = {int:id_user}',
[
'id_user' => $id_user,
]);
if (empty($row))
return false;
list($subject, $body) = self::parseTemplate([
'subject' => 'Information on how to reset your HashRU password',
'body' => str_replace("\n", "\r\n", 'Dear %FIRST_NAME%,
You are receiving this email because a password reset request was issued on our website.
If you did not request a password reset, please disregard this email. Otherwise, please follow the link below.
%RESET_LINK%
The HashRU Pics team'),
], [
'FIRST_NAME' => $row['first_name'],
'RESET_LINK' => BASEURL . '/resetpassword/?step=2&email=' . rawurlencode($row['emailaddress']) . '&key=' . $row['reset_key'],
]);
$addressee = trim($row['first_name'] . ' ' . $row['surname']);
self::send($row['emailaddress'], $addressee, $subject, $body);
}
}

View File

@@ -3,7 +3,7 @@
* ErrorHandler.php
* Contains key class ErrorHandler.
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
* Kabuki CMS (C) 2013-2016, Aaron van Geffen
*****************************************************************************/
class ErrorHandler
@@ -11,6 +11,18 @@ class ErrorHandler
private static $error_count = 0;
private static $handling_error;
public static function enable()
{
set_error_handler('ErrorHandler::handleError');
ini_set("display_errors", DEBUG ? "On" : "Off");
}
public static function disable()
{
set_error_handler(NULL);
ini_set("display_errors", "Off");
}
// Handler for standard PHP error messages.
public static function handleError($error_level, $error_message, $file, $line, $context = null)
{
@@ -121,14 +133,27 @@ class ErrorHandler
return $error_message;
}
public static function display($message, $debug_info)
public static function display($message, $debug_info, $is_sensitive = true)
{
$is_admin = Registry::has('user') && Registry::get('user')->isAdmin();
// Just show the message if we're running in a console.
if (empty($_SERVER['HTTP_HOST']))
{
echo $message;
exit;
}
// JSON request?
elseif (isset($_GET['json']) || isset($_GET['format']) && $_GET['format'] == 'json')
{
if (DEBUG || $is_admin)
echo json_encode(['error' => $message . "\n\n" . $debug_info]);
elseif (!$is_sensitive)
echo json_encode(['error' => $message]);
else
echo json_encode(['error' => 'Our apologies, an error occured while we were processing your request. Please try again later, or contact us if the problem persists.']);
exit;
}
// Initialise the main template to present a nice message to the user.
$page = new MainTemplate('An error occured!');
@@ -146,6 +171,8 @@ class ErrorHandler
$page->adopt(new AdminBar());
}
}
elseif (!$is_sensitive)
$page->adopt(new DummyBox('An error occured!', '<p>' . $message . '</p>'));
else
$page->adopt(new DummyBox('An error occured!', '<p>Our apologies, an error occured while we were processing your request. Please try again later, or contact us if the problem persists.</p>'));

View File

@@ -0,0 +1,12 @@
<?php
/*****************************************************************************
* UserFacingException.php
* Contains exception class UserFacingException.
*
* Kabuki CMS (C) 2013-2016, Aaron van Geffen
*****************************************************************************/
class UserFacingException extends Exception
{
}