Start writing password reset

This commit is contained in:
Robbie Antenesse 2019-05-31 17:24:41 -06:00
parent 1735c00d53
commit f112e3b143
4 changed files with 168 additions and 0 deletions

View File

@ -0,0 +1,59 @@
import { setupInfoModal } from "../setupListeners";
import { request } from "./helpers";
export function renderForgotPasswordForm() {
const modal = document.createElement('section');
modal.classList.add('modal');
modal.innerHTML = `<div class="modal-background"></div>
<div class="modal-content">
<a class="close-button">&times;&#xFE0E;</a>
<section class="info-modal" id="forgotPasswordForm">
<h2>Forgot Password</h2>
<p>Enter the email address associated with your Lexiconga account to initiate a password reset.</p>
<label>Email<br>
<input type="email" id="forgotPasswordEmailField" style="max-width:250px;" maxlength="100">
</label>
<section id="forgotPasswordErrorMessages"></section>
<button class="button" id="forgotPasswordSubmit">Email Password Reset Key</button>
</section>
</div>`;
document.body.appendChild(modal);
setupStartResetForm();
setupInfoModal(modal);
}
function setupStartResetForm() {
document.getElementById('forgotPasswordSubmit').addEventListener('click', sendPasswordReset);
}
function sendPasswordReset() {
const email = document.getElementById('forgotPasswordEmailField').value.trim();
const errorMessageElement = document.getElementById('forgotPasswordErrorMessages');
let errorMessage = '';
if (email === '') {
errorMessage += '<p class="red bold">Please enter an email address.</p>';
}
errorMessageElement.innerHTML = errorMessage;
if (errorMessage === '') {
request({
action: 'initiate-password-reset',
email,
}, success => {
console.log(success);
}, error => {
errorMessage += '<p class="red bold">' + error + '</p>';
}).then(() => {
errorMessageElement.innerHTML = errorMessage;
if (errorMessage === '') {
document.getElementById('forgotPasswordForm').innerHTML = `<h2>Password Reset Key Sent</h2>
<p>Go check your email for the password reset link.</p>
<p><em>Note that it may be sent to your spam/junk folder by mistake.</em></p>`;
}
});
}
}

View File

@ -2,6 +2,7 @@ import { logIn, createAccount } from "./login";
import { setCookie } from "../StackOverflow/cookie"; import { setCookie } from "../StackOverflow/cookie";
import { changeDictionary, createNewDictionary } from "./dictionaryManagement"; import { changeDictionary, createNewDictionary } from "./dictionaryManagement";
import { addMessage } from "../utilities"; import { addMessage } from "../utilities";
import { renderForgotPasswordForm } from "./passwordReset";
export function setupLoginModal(modal) { export function setupLoginModal(modal) {
const closeElements = modal.querySelectorAll('.modal-background, .close-button'); const closeElements = modal.querySelectorAll('.modal-background, .close-button');
@ -36,6 +37,7 @@ export function setupLoginModal(modal) {
}); });
document.getElementById('loginSubmit').addEventListener('click', logIn); document.getElementById('loginSubmit').addEventListener('click', logIn);
document.getElementById('forgotPasswordButton').addEventListener('click', renderForgotPasswordForm);
document.getElementById('createAccountSubmit').addEventListener('click', createAccount); document.getElementById('createAccountSubmit').addEventListener('click', createAccount);
} }

View File

@ -267,6 +267,74 @@ VALUES (?, ?, ?, ?, ?)';
return false; return false;
} }
public function setPasswordReset($email) {
$date = date("Y-m-d H:i:s");
$reset_code = random_int(0, 999999999);
$reset_code_hash = $this->token->hash($reset_code);
$query = "UPDATE `users` SET `password_reset_code`=?, `password_reset_date`=? WHERE `email`=?;";
$reset = $this->db->execute($query, array(
$reset_code,
$date,
$email,
));
if ($reset) {
$user_data = $this->getUserDataByEmailForPasswordReset($email);
if ($user_data) {
$to = $email;
$subject = "Here's your Lexiconga password reset link";
$message = "Hello " . $user_data['public_name'] . "\r\n\r\nSomeone has requested a password reset link for your Lexiconga account. If it was you, you can reset your password by going to the link below and entering a new password for yourself:\r\n";
$message .= "http://lexicon.ga/passwordreset?account=" . $user_data['id'] . "&code=" . $reset_code_hash . "\r\n\r\n";
$message .= "If it wasn't you who requested the link, you can ignore this email since it was only sent to you, but you might want to consider changing your password when you have a chance.\r\n\r\n";
$message .= "The password link will only be valid for today until you use it.\r\n\r\n";
$message .= "Thanks!\r\nThe Lexiconga Admins";
$header = "From: Lexiconga Password Reset <donotreply@lexicon.ga>\r\n"
. "Reply-To: help@lexicon.ga\r\n"
. "X-Mailer: PHP/" . phpversion();
if (mail($to, $subject, $message, $header)) {
return true;
} else {
return array(
'error' => 'Could not send email to ' . $email,
);
}
}
} else {
return array(
'error' => $this->db->last_error_info,
);
}
return false;
}
public function checkPasswordReset($id, $code) {
$date = date("Y-m-d");
$daterange = "'" . $date . " 00:00:00' AND '" . $date . " 23:59:59'";
$unhashed_code = $this->token->unhash($code);
$query = "SELECT * FROM `users` WHERE `id`=? AND `password_reset_code`=? AND `password_reset_date` BETWEEN " . $daterange . ";";
$stmt = $this->db->query($query, array(
$id,
$unhashed_code,
));
$results = $stmt->fetchAll();
if ($stmt && $results) {
return count($results) === 1;
} else {
return false;
}
}
public function resetPassword($password, $email) {
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$query = "UPDATE `users` SET `password`=?, `password_reset_date`='0000-00-00 00:00:00' WHERE `email`=?;";
return $this->db->execute($query, array(
$password_hash,
$email,
));
}
private function generateUserToken ($user_id, $dictionary_id) { private function generateUserToken ($user_id, $dictionary_id) {
$user_data = array( $user_data = array(
'id' => intval($user_id), 'id' => intval($user_id),
@ -287,4 +355,17 @@ VALUES (?, ?, ?, ?, ?)';
$stmt = $this->db->query($update_query, array($new_password)); $stmt = $this->db->query($update_query, array($new_password));
return $stmt->rowCount() === 1; return $stmt->rowCount() === 1;
} }
private function getUserDataByEmailForPasswordReset($email) {
$query = 'SELECT id, public_name FROM users WHERE email=?';
$stmt = $this->db->query($query, array($email));
$result = $stmt->fetch();
if ($stmt && $result) {
return array(
'id' => $result['id'],
'public_name' => $result['public_name'] ? $result['public_name'] : 'Lexiconger',
);
}
return false;
}
} }

View File

@ -418,6 +418,32 @@ switch ($action) {
'error' => true, 'error' => true,
), 400); ), 400);
} }
case 'initiate-password-reset': {
if (isset($request['email'])) {
$user = new User();
$password_reset = $user->setPasswordReset($request['email']);
if ($password_reset === true) {
return Response::json(array(
'data' => $password_reset,
'error' => false,
), 200);
}
if (isset($password_reset['error'])) {
return Response::json(array(
'data' => $password_reset['error'],
'error' => true,
), 500);
}
return Response::json(array(
'data' => 'Could not send password reset key: email not found',
'error' => true,
), 401);
}
return Response::json(array(
'data' => 'Could not send password reset key: required data missing',
'error' => true,
), 400);
}
default: { default: {
return Response::html('Hi!'); return Response::html('Hi!');