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

@@ -6,6 +6,8 @@ require_once("functions.php");
require_once 'google-client/vendor/autoload.php'; // Add this line for Google Client
use Middleware\CsrfMiddleware;
use Middleware\RateLimitMiddleware;
use Services\AuthenticationService;
// Check if connection is established
if (!$conn) {
@@ -64,6 +66,10 @@ if (isset($_GET['code'])) {
$_SESSION['first_name'] = $first_name;
$_SESSION['profile_pic'] = $picture;
processLegacyMembership($_SESSION['user_id']);
// Regenerate session to prevent session fixation attacks
AuthenticationService::regenerateSession();
// Reset rate limit on successful login
RateLimitMiddleware::reset('login');
// echo json_encode(['status' => 'success', 'message' => 'Google login successful']);
header("Location: index.php");
exit();
@@ -79,6 +85,10 @@ if (isset($_GET['code'])) {
$_SESSION['first_name'] = $row['first_name'];
$_SESSION['profile_pic'] = $row['profile_pic'];
sendEmail('chrispintoza@gmail.com', '4WDCSA: New User Login', $name.' has just logged in using Google Login.');
// Regenerate session to prevent session fixation attacks
AuthenticationService::regenerateSession();
// Reset rate limit on successful login
RateLimitMiddleware::reset('login');
// echo json_encode(['status' => 'success', 'message' => 'Google login successful']);
header("Location: index.php");
exit();
@@ -93,6 +103,17 @@ if (isset($_GET['code'])) {
// Check if email and password login is requested
if (isset($_POST['email']) && isset($_POST['password'])) {
// Check rate limit first (5 attempts per 15 minutes)
if (RateLimitMiddleware::isLimited('login', 5, 900)) {
$remaining = RateLimitMiddleware::getTimeRemaining('login', 900);
echo json_encode([
'status' => 'error',
'message' => "Too many login attempts. Please try again in {$remaining} seconds.",
'retry_after' => $remaining
]);
exit();
}
// Retrieve and sanitize form data
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
$password = trim($_POST['password']); // Remove extra spaces
@@ -100,11 +121,13 @@ if (isset($_POST['email']) && isset($_POST['password'])) {
// Validate input
if (empty($email) || empty($password)) {
echo json_encode(['status' => 'error', 'message' => 'Please enter both email and password.']);
RateLimitMiddleware::incrementAttempt('login', 900);
exit();
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']);
RateLimitMiddleware::incrementAttempt('login', 900);
exit();
}
@@ -128,6 +151,7 @@ if (isset($_POST['email']) && isset($_POST['password'])) {
// Check if the user is verified
if ($row['is_verified'] == 0) {
echo json_encode(['status' => 'error', 'message' => 'Your account is not verified. Please check your email for the verification link.']);
RateLimitMiddleware::incrementAttempt('login', 900);
exit();
}
@@ -136,13 +160,19 @@ if (isset($_POST['email']) && isset($_POST['password'])) {
$_SESSION['user_id'] = $row['user_id']; // Adjust as per your table structure
$_SESSION['first_name'] = $row['first_name']; // Adjust as per your table structure
$_SESSION['profile_pic'] = $row['profile_pic'];
// Regenerate session to prevent session fixation attacks
AuthenticationService::regenerateSession();
// Reset rate limit on successful login
RateLimitMiddleware::reset('login');
echo json_encode(['status' => 'success', 'message' => 'Successful Login']);
} else {
// Password is incorrect
// Password is incorrect - increment rate limit
RateLimitMiddleware::incrementAttempt('login', 900);
echo json_encode(['status' => 'error', 'message' => 'Invalid password.']);
}
} else {
// User does not exist
// User does not exist - still increment rate limit to prevent email enumeration
RateLimitMiddleware::incrementAttempt('login', 900);
echo json_encode(['status' => 'error', 'message' => 'User with that email does not exist.']);
}