5 && (time() - $_SESSION['first_attempt_time']) < 900) { // 15 minutes $_SESSION['error'] = "Too many attempts. Please try again later."; header("Location: forgot-password.php?code=" . urlencode($_POST['verification_code'])); exit; } if (time() - $_SESSION['first_attempt_time'] >= 900) { $_SESSION['attempts'] = 1; $_SESSION['first_attempt_time'] = time(); } // Process the reset. $verification_code = trim($_POST['verification_code']); $username = trim($_POST['username'] ?? ''); $new_password = $_POST['new_password'] ?? ''; $confirm_password = $_POST['confirm_password'] ?? ''; if (empty($verification_code) || empty($username) || empty($new_password) || empty($confirm_password)) { $_SESSION['error'] = $locale['allFieldsRequired']; header("Location: forgot-password.php?code=" . urlencode($verification_code)); exit; } if ($new_password !== $confirm_password) { $_SESSION['error'] = $locale['passwordMismatch']; header("Location: forgot-password.php?code=" . urlencode($verification_code)); exit; } if (!validate_password($new_password)) { $_SESSION['error'] = $locale['passwordRequirements']; header("Location: forgot-password.php?code=" . urlencode($verification_code)); exit; } // Look up the password reset record. $stmt = $db->prepare("SELECT * FROM email_verifications WHERE verification_code = ? AND purpose = 'password_reset'"); $stmt->bind_param("s", $verification_code); $stmt->execute(); $result = $stmt->get_result(); $record = $result->fetch_assoc(); $stmt->close(); if (!$record) { $_SESSION['error'] = $locale['resetExpiredInvalid']; header("Location: forgot-password.php?code=" . urlencode($verification_code)); exit; } // Check expiration. $current_time = new DateTime(); $expires_at = new DateTime($record['expires_at']); if ($current_time > $expires_at) { $_SESSION['error'] = $locale['resetExpired']; header("Location: forgot-password.php?code=" . urlencode($verification_code)); exit; } // Verify the username matches the record. $stmt = $db->prepare("SELECT id, username FROM users WHERE id = ? AND username = ?"); $stmt->bind_param("is", $record['user_id'], $username); $stmt->execute(); $userData = $stmt->get_result()->fetch_assoc(); $stmt->close(); if (!$userData) { $_SESSION['error'] = $locale['codeCredsInvalid']; header("Location: forgot-password.php?code=" . urlencode($verification_code)); exit; } // Update the user's password. $hashed_password = password_hash($new_password, PASSWORD_DEFAULT); $stmt = $db->prepare("UPDATE users SET password = ? WHERE id = ?"); $stmt->bind_param("si", $hashed_password, $userData['id']); $stmt->execute(); $stmt->close(); // Remove the password reset record. $stmt = $db->prepare("DELETE FROM email_verifications WHERE verification_code = ? AND purpose = 'password_reset'"); $stmt->bind_param("s", $verification_code); $stmt->execute(); $stmt->close(); session_regenerate_id(true); $_SESSION['success'] = $locale['passwordResetSuccess']; header("Location: /login"); exit; } else { // Otherwise, we are processing a forgot password request. $email = trim($_POST['email'] ?? ''); if (empty($email)) { $_SESSION['error'] = $locale['enterEmailAddressPlease']; header("Location: /forgot-password"); exit; } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $_SESSION['error'] = $locale['emailInvalid']; header("Location: /forgot-password"); exit; } // Check if email exists in the system. $stmt = $db->prepare("SELECT id, username FROM users WHERE email = ?"); $stmt->bind_param("s", $email); $stmt->execute(); $result = $stmt->get_result(); $userData = $result->fetch_assoc(); $stmt->close(); // Always display a success message (even if the email isn’t registered) to avoid disclosing registered emails. $_SESSION['success'] = $locale['passwordResetSent']; if ($userData) { $user_id = $userData['id']; // Generate a password reset verification code valid for 15 minutes. $verification_code = bin2hex(random_bytes(16)); $expires_at = date("Y-m-d H:i:s", strtotime("+15 minutes")); // Insert a record for the password reset. $stmt = $db->prepare("REPLACE INTO email_verifications (user_id, email, verification_code, expires_at, purpose) VALUES (?, ?, ?, ?, 'password_reset')"); $stmt->bind_param("isss", $user_id, $email, $verification_code, $expires_at); $stmt->execute(); $stmt->close(); // Send the password reset email via AWS SES. $sesClient = new SesClient([ 'version' => 'latest', 'region' => $config['aws']['ses']['region'], 'credentials' => [ 'key' => $config['aws']['ses']['access_key'], 'secret' => $config['aws']['ses']['secret_key'], ] ]); $sender_email = $config['aws']['ses']['sender_email']; $recipient_email = $email; $subject = "Password Reset Request"; $reset_link = $config['app']['url'] . "/forgot-password.php?code={$verification_code}"; $body_text = $locale['passwordResetRequested'] . "\n\n"; $body_text .= "{$reset_link}\n\n"; $body_text .= $locale['passwordResetUnrequested']; try { $sesClient->sendEmail([ 'Destination' => [ 'ToAddresses' => [$recipient_email], ], 'ReplyToAddresses' => [$sender_email], 'Source' => $sender_email, 'Message' => [ 'Body' => [ 'Text' => [ 'Charset' => 'UTF-8', 'Data' => $body_text, ], ], 'Subject' => [ 'Charset' => 'UTF-8', 'Data' => $subject, ], ], ]); } catch (AwsException $e) { // Log the error as needed. } } header("Location: /forgot-password"); exit; } } // Helper function to validate password strength. function validate_password($password) { if (strlen($password) < 8) return false; if (!preg_match('/[A-Z]/', $password)) return false; if (!preg_match('/[a-z]/', $password)) return false; if (!preg_match('/[0-9]/', $password)) return false; return true; } require_once 'includes/header.php'; ?>
' . htmlspecialchars($_SESSION['error']) . '
'; unset($_SESSION['error']); } if (isset($_SESSION['success'])) { echo ''; unset($_SESSION['success']); } ?>