db = $database; } else { $this->db = $database->getConnection(); } } /** * Creates a password reset request and sends email to user * * @param string $email User's email address * @return array Status of the reset request */ public function requestReset($email) { // Check if email exists $query = $this->db->prepare(" SELECT u.id, um.email FROM user u JOIN user_meta um ON u.id = um.user_id WHERE um.email = :email" ); $query->bindParam(':email', $email); $query->execute(); $user = $query->fetch(PDO::FETCH_ASSOC); if (!$user) { return ['success' => false, 'message' => 'If this email exists in our system, you will receive reset instructions.']; } // Generate unique token $token = bin2hex(random_bytes(self::TOKEN_LENGTH / 2)); $expires = time() + self::TOKEN_EXPIRY; // Store token in database $query = $this->db->prepare(" INSERT INTO user_password_reset (user_id, token, expires) VALUES (:user_id, :token, :expires)" ); $query->bindParam(':user_id', $user['id']); $query->bindParam(':token', $token); $query->bindParam(':expires', $expires); if (!$query->execute()) { return ['success' => false, 'message' => 'Failed to process reset request']; } // We need the config for the email details global $config; // Prepare the reset link $scheme = $_SERVER['REQUEST_SCHEME']; $domain = trim($config['domain'], '/'); $folder = trim($config['folder'], '/'); $folderPath = $folder !== '' ? "/$folder" : ''; $resetLink = "{$scheme}://{$domain}{$folderPath}/index.php?page=login&action=reset&token=" . urlencode($token); // Send email with reset link $to = $user['email']; // Load email helper require_once __DIR__ . '/../helpers/email_helper.php'; $subject = "{$config['site_name']} - Password reset request"; $variables = [ 'site_name' => $config['site_name'], 'reset_link' => $resetLink, 'site_slogan' => $config['site_slogan'] ?? '' ]; $additionalHeaders = [ 'From' => "noreply@{$config['domain']}", 'Reply-To' => "noreply@{$config['domain']}" ]; if (!sendTemplateEmail($to, $subject, 'password_reset', $variables, $config, $additionalHeaders)) { return ['success' => false, 'message' => 'Failed to send reset email']; } return ['success' => true, 'message' => 'If this email exists in our system, you will receive reset instructions.']; } /** * Validates a reset token and returns associated user ID if valid * * @param string $token Reset token * @return array Validation result with user ID if successful */ public function validateToken($token) { $now = time(); $query = $this->db->prepare(" SELECT user_id FROM user_password_reset WHERE token = :token AND expires > :now AND used = 0 "); $query->bindParam(':token', $token); $query->bindParam(':now', $now); $query->execute(); $result = $query->fetch(PDO::FETCH_ASSOC); if (!$result) { return ['valid' => false]; } return ['valid' => true, 'user_id' => $result['user_id']]; } /** * Completes the password reset process * * @param string $token Reset token * @param string $newPassword New password * @return bool Whether reset was successful */ public function resetPassword($token, $newPassword) { $validation = $this->validateToken($token); if (!$validation['valid']) { return false; } // Start transaction $this->db->beginTransaction(); try { // Update password $hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT); $query = $this->db->prepare( "UPDATE user SET password = :password WHERE id = :user_id" ); $query->bindParam(':password', $hashedPassword); $query->bindParam(':user_id', $validation['user_id']); $query->execute(); // Mark token as used $query = $this->db->prepare( "UPDATE user_password_reset SET used = 1 WHERE token = :token" ); $query->bindParam(':token', $token); $query->execute(); $this->db->commit(); return true; } catch (Exception $e) { $this->db->rollBack(); return false; } } }