- Created RateLimitMiddleware class with 8 public methods * isLimited() - check if limit exceeded * incrementAttempt() - increment attempt counter * getRemainingAttempts() - get remaining attempts * getTimeRemaining() - get time remaining in window * reset() - reset counter after success * requireLimit() - check and die if exceeded * getStatus() - get status info for monitoring * Support for time-window based rate limiting - Integrated rate limiting into critical endpoints: * validate_login.php: 5 attempts per 900 seconds (15 minutes) * send_reset_link.php: 3 attempts per 1800 seconds (30 minutes) * Prevents brute force attacks and password reset abuse * Still increments counter for non-existent emails (prevents enumeration) - Integrated session regeneration on successful login: * Google OAuth login (both new and existing users) * Email/password login * Uses AuthenticationService::regenerateSession() * Prevents session fixation attacks - Rate limit counters stored in PHP session - Time-window based with 15-minute and 30-minute windows - Graceful error messages with retry_after in JSON responses - AJAX-aware error handling
65 lines
2.4 KiB
PHP
65 lines
2.4 KiB
PHP
<?php
|
|
require_once("env.php");
|
|
require_once("connection.php");
|
|
require_once("functions.php");
|
|
|
|
use Middleware\RateLimitMiddleware;
|
|
|
|
$response = array('status' => 'error', 'message' => 'Something went wrong');
|
|
|
|
if (isset($_POST['email'])) {
|
|
// Check rate limit first (3 attempts per 30 minutes to prevent abuse)
|
|
if (RateLimitMiddleware::isLimited('password_reset', 3, 1800)) {
|
|
$remaining = RateLimitMiddleware::getTimeRemaining('password_reset', 1800);
|
|
$response['status'] = 'error';
|
|
$response['message'] = "Too many password reset requests. Please try again in {$remaining} seconds.";
|
|
$response['retry_after'] = $remaining;
|
|
echo json_encode($response);
|
|
exit();
|
|
}
|
|
|
|
$email = $_POST['email'];
|
|
|
|
// Check if the email exists
|
|
$sql = "SELECT user_id FROM users WHERE email = ?";
|
|
$stmt = $conn->prepare($sql);
|
|
$stmt->bind_param("s", $email);
|
|
$stmt->execute();
|
|
$result = $stmt->get_result();
|
|
|
|
if ($result->num_rows > 0) {
|
|
$user = $result->fetch_assoc();
|
|
$user_id = $user['user_id'];
|
|
|
|
// Generate a unique token
|
|
$token = bin2hex(random_bytes(50));
|
|
|
|
// Store the token and expiration time in the database
|
|
$expiry = date("Y-m-d H:i:s", strtotime('+3 hour')); // Token expires in 3 hour
|
|
$sql = "INSERT INTO password_resets (user_id, token, expires_at) VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE token = VALUES(token), expires_at = VALUES(expires_at)";
|
|
$stmt = $conn->prepare($sql);
|
|
$stmt->bind_param("iss", $user_id, $token, $expiry);
|
|
$stmt->execute();
|
|
|
|
// Send the reset link to the user
|
|
$reset_link = "https://www.4wdcsa.co.za/reset_password.php?token=$token";
|
|
$subject = "Password Reset Request";
|
|
$message = "Click the following link to reset your password: $reset_link";
|
|
sendEmail($email, $subject, $message);
|
|
|
|
// Reset rate limit on successful request
|
|
RateLimitMiddleware::reset('password_reset');
|
|
|
|
$response['status'] = 'success';
|
|
$response['message'] = 'Password reset link has been sent to your email.';
|
|
} else {
|
|
// Increment rate limit even for non-existent emails (prevent email enumeration)
|
|
RateLimitMiddleware::incrementAttempt('password_reset', 1800);
|
|
$response['message'] = 'Email not found.';
|
|
}
|
|
}
|
|
|
|
echo json_encode($response);
|
|
?>
|