Phase 2: Add rate limiting and session regeneration

- 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
This commit is contained in:
twotalesanimation
2025-12-02 21:10:48 +02:00
parent a311e81a12
commit a4526979c4
3 changed files with 334 additions and 3 deletions

View File

@@ -3,9 +3,21 @@ 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
@@ -23,7 +35,7 @@ if (isset($_POST['email'])) {
$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 1 hour
$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);
@@ -36,9 +48,14 @@ if (isset($_POST['email'])) {
$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.';
}
}