diff --git a/src/js/account/passwordReset.js b/src/js/account/passwordReset.js new file mode 100644 index 0000000..3b5b730 --- /dev/null +++ b/src/js/account/passwordReset.js @@ -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 = ` + `; + + 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 += '

Please enter an email address.

'; + } + + errorMessageElement.innerHTML = errorMessage; + + if (errorMessage === '') { + request({ + action: 'initiate-password-reset', + email, + }, success => { + console.log(success); + }, error => { + errorMessage += '

' + error + '

'; + }).then(() => { + errorMessageElement.innerHTML = errorMessage; + if (errorMessage === '') { + document.getElementById('forgotPasswordForm').innerHTML = `

Password Reset Key Sent

+

Go check your email for the password reset link.

+

Note that it may be sent to your spam/junk folder by mistake.

`; + } + }); + } +} \ No newline at end of file diff --git a/src/js/account/setupListeners.js b/src/js/account/setupListeners.js index b3bf316..d8fec90 100644 --- a/src/js/account/setupListeners.js +++ b/src/js/account/setupListeners.js @@ -2,6 +2,7 @@ import { logIn, createAccount } from "./login"; import { setCookie } from "../StackOverflow/cookie"; import { changeDictionary, createNewDictionary } from "./dictionaryManagement"; import { addMessage } from "../utilities"; +import { renderForgotPasswordForm } from "./passwordReset"; export function setupLoginModal(modal) { const closeElements = modal.querySelectorAll('.modal-background, .close-button'); @@ -36,6 +37,7 @@ export function setupLoginModal(modal) { }); document.getElementById('loginSubmit').addEventListener('click', logIn); + document.getElementById('forgotPasswordButton').addEventListener('click', renderForgotPasswordForm); document.getElementById('createAccountSubmit').addEventListener('click', createAccount); } diff --git a/src/php/api/User.php b/src/php/api/User.php index b52beda..613b6e6 100644 --- a/src/php/api/User.php +++ b/src/php/api/User.php @@ -267,6 +267,74 @@ VALUES (?, ?, ?, ?, ?)'; 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 \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) { $user_data = array( 'id' => intval($user_id), @@ -287,4 +355,17 @@ VALUES (?, ?, ?, ?, ?)'; $stmt = $this->db->query($update_query, array($new_password)); 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; + } } \ No newline at end of file diff --git a/src/php/api/index.php b/src/php/api/index.php index fdb76be..1d05666 100644 --- a/src/php/api/index.php +++ b/src/php/api/index.php @@ -418,6 +418,32 @@ switch ($action) { 'error' => true, ), 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: { return Response::html('Hi!');