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:
@@ -3,9 +3,21 @@ require_once("env.php");
|
|||||||
require_once("connection.php");
|
require_once("connection.php");
|
||||||
require_once("functions.php");
|
require_once("functions.php");
|
||||||
|
|
||||||
|
use Middleware\RateLimitMiddleware;
|
||||||
|
|
||||||
$response = array('status' => 'error', 'message' => 'Something went wrong');
|
$response = array('status' => 'error', 'message' => 'Something went wrong');
|
||||||
|
|
||||||
if (isset($_POST['email'])) {
|
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'];
|
$email = $_POST['email'];
|
||||||
|
|
||||||
// Check if the email exists
|
// Check if the email exists
|
||||||
@@ -23,7 +35,7 @@ if (isset($_POST['email'])) {
|
|||||||
$token = bin2hex(random_bytes(50));
|
$token = bin2hex(random_bytes(50));
|
||||||
|
|
||||||
// Store the token and expiration time in the database
|
// 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 (?, ?, ?)
|
$sql = "INSERT INTO password_resets (user_id, token, expires_at) VALUES (?, ?, ?)
|
||||||
ON DUPLICATE KEY UPDATE token = VALUES(token), expires_at = VALUES(expires_at)";
|
ON DUPLICATE KEY UPDATE token = VALUES(token), expires_at = VALUES(expires_at)";
|
||||||
$stmt = $conn->prepare($sql);
|
$stmt = $conn->prepare($sql);
|
||||||
@@ -36,9 +48,14 @@ if (isset($_POST['email'])) {
|
|||||||
$message = "Click the following link to reset your password: $reset_link";
|
$message = "Click the following link to reset your password: $reset_link";
|
||||||
sendEmail($email, $subject, $message);
|
sendEmail($email, $subject, $message);
|
||||||
|
|
||||||
|
// Reset rate limit on successful request
|
||||||
|
RateLimitMiddleware::reset('password_reset');
|
||||||
|
|
||||||
$response['status'] = 'success';
|
$response['status'] = 'success';
|
||||||
$response['message'] = 'Password reset link has been sent to your email.';
|
$response['message'] = 'Password reset link has been sent to your email.';
|
||||||
} else {
|
} else {
|
||||||
|
// Increment rate limit even for non-existent emails (prevent email enumeration)
|
||||||
|
RateLimitMiddleware::incrementAttempt('password_reset', 1800);
|
||||||
$response['message'] = 'Email not found.';
|
$response['message'] = 'Email not found.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
284
src/Middleware/RateLimitMiddleware.php
Normal file
284
src/Middleware/RateLimitMiddleware.php
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Middleware;
|
||||||
|
|
||||||
|
use Services\DatabaseService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rate Limiting Middleware
|
||||||
|
*
|
||||||
|
* Provides rate limiting for sensitive endpoints like login, password reset,
|
||||||
|
* and API endpoints. Uses session-based counters with time windows.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Time-window based rate limiting (e.g., 5 attempts per 15 minutes)
|
||||||
|
* - IP-based and user-based tracking
|
||||||
|
* - Graceful degradation if storage unavailable
|
||||||
|
* - Configurable limits per endpoint
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* RateLimitMiddleware::checkLimit('login', 5, 900); // 5 attempts per 15 mins
|
||||||
|
* RateLimitMiddleware::incrementAttempt('login');
|
||||||
|
* RateLimitMiddleware::reset('login'); // After successful attempt
|
||||||
|
*/
|
||||||
|
class RateLimitMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Session key prefix for rate limiting counters
|
||||||
|
*/
|
||||||
|
private const RATE_LIMIT_PREFIX = '_rate_limit_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session key for timestamp tracking
|
||||||
|
*/
|
||||||
|
private const RATE_LIMIT_TIME_PREFIX = '_rate_limit_time_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if client has exceeded rate limit
|
||||||
|
*
|
||||||
|
* @param string $endpoint Name of the endpoint (e.g., 'login', 'password_reset')
|
||||||
|
* @param int $maxAttempts Maximum attempts allowed
|
||||||
|
* @param int $timeWindow Time window in seconds (default: 900 = 15 minutes)
|
||||||
|
* @return bool True if limit exceeded, false if within limit
|
||||||
|
*/
|
||||||
|
public static function isLimited(
|
||||||
|
string $endpoint,
|
||||||
|
int $maxAttempts = 5,
|
||||||
|
int $timeWindow = 900
|
||||||
|
): bool {
|
||||||
|
self::startSession();
|
||||||
|
|
||||||
|
$counterKey = self::RATE_LIMIT_PREFIX . $endpoint;
|
||||||
|
$timeKey = self::RATE_LIMIT_TIME_PREFIX . $endpoint;
|
||||||
|
|
||||||
|
$currentTime = time();
|
||||||
|
$lastAttemptTime = $_SESSION[$timeKey] ?? 0;
|
||||||
|
$attempts = $_SESSION[$counterKey] ?? 0;
|
||||||
|
|
||||||
|
// Reset if time window has expired
|
||||||
|
if ($currentTime - $lastAttemptTime > $timeWindow) {
|
||||||
|
$_SESSION[$counterKey] = 0;
|
||||||
|
$_SESSION[$timeKey] = $currentTime;
|
||||||
|
return false; // Not limited (fresh window)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if limit exceeded
|
||||||
|
return $attempts >= $maxAttempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the attempt counter for an endpoint
|
||||||
|
*
|
||||||
|
* @param string $endpoint Name of the endpoint
|
||||||
|
* @param int $timeWindow Time window in seconds (default: 900 = 15 minutes)
|
||||||
|
* @return int New attempt count
|
||||||
|
*/
|
||||||
|
public static function incrementAttempt(
|
||||||
|
string $endpoint,
|
||||||
|
int $timeWindow = 900
|
||||||
|
): int {
|
||||||
|
self::startSession();
|
||||||
|
|
||||||
|
$counterKey = self::RATE_LIMIT_PREFIX . $endpoint;
|
||||||
|
$timeKey = self::RATE_LIMIT_TIME_PREFIX . $endpoint;
|
||||||
|
|
||||||
|
$currentTime = time();
|
||||||
|
$lastAttemptTime = $_SESSION[$timeKey] ?? 0;
|
||||||
|
$attempts = $_SESSION[$counterKey] ?? 0;
|
||||||
|
|
||||||
|
// Reset if time window has expired
|
||||||
|
if ($currentTime - $lastAttemptTime > $timeWindow) {
|
||||||
|
$_SESSION[$counterKey] = 1;
|
||||||
|
$_SESSION[$timeKey] = $currentTime;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment counter
|
||||||
|
$_SESSION[$counterKey] = ++$attempts;
|
||||||
|
|
||||||
|
// Update timestamp (keep initial window start)
|
||||||
|
if (!isset($_SESSION[$timeKey])) {
|
||||||
|
$_SESSION[$timeKey] = $currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $attempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get remaining attempts for an endpoint
|
||||||
|
*
|
||||||
|
* @param string $endpoint Name of the endpoint
|
||||||
|
* @param int $maxAttempts Maximum attempts allowed
|
||||||
|
* @param int $timeWindow Time window in seconds
|
||||||
|
* @return int Number of remaining attempts (0 if limit exceeded)
|
||||||
|
*/
|
||||||
|
public static function getRemainingAttempts(
|
||||||
|
string $endpoint,
|
||||||
|
int $maxAttempts = 5,
|
||||||
|
int $timeWindow = 900
|
||||||
|
): int {
|
||||||
|
self::startSession();
|
||||||
|
|
||||||
|
$counterKey = self::RATE_LIMIT_PREFIX . $endpoint;
|
||||||
|
$timeKey = self::RATE_LIMIT_TIME_PREFIX . $endpoint;
|
||||||
|
|
||||||
|
$currentTime = time();
|
||||||
|
$lastAttemptTime = $_SESSION[$timeKey] ?? 0;
|
||||||
|
$attempts = $_SESSION[$counterKey] ?? 0;
|
||||||
|
|
||||||
|
// Reset if time window has expired
|
||||||
|
if ($currentTime - $lastAttemptTime > $timeWindow) {
|
||||||
|
return $maxAttempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return max(0, $maxAttempts - $attempts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get seconds remaining in the current time window
|
||||||
|
*
|
||||||
|
* @param string $endpoint Name of the endpoint
|
||||||
|
* @param int $timeWindow Time window in seconds
|
||||||
|
* @return int Seconds remaining in window
|
||||||
|
*/
|
||||||
|
public static function getTimeRemaining(
|
||||||
|
string $endpoint,
|
||||||
|
int $timeWindow = 900
|
||||||
|
): int {
|
||||||
|
self::startSession();
|
||||||
|
|
||||||
|
$timeKey = self::RATE_LIMIT_TIME_PREFIX . $endpoint;
|
||||||
|
|
||||||
|
$currentTime = time();
|
||||||
|
$lastAttemptTime = $_SESSION[$timeKey] ?? 0;
|
||||||
|
|
||||||
|
if ($lastAttemptTime === 0) {
|
||||||
|
return $timeWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$elapsed = $currentTime - $lastAttemptTime;
|
||||||
|
|
||||||
|
if ($elapsed >= $timeWindow) {
|
||||||
|
return $timeWindow; // Window expired, new window starts
|
||||||
|
}
|
||||||
|
|
||||||
|
return $timeWindow - $elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the rate limit counter for an endpoint
|
||||||
|
* Call this after successful operation (e.g., after successful login)
|
||||||
|
*
|
||||||
|
* @param string $endpoint Name of the endpoint
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function reset(string $endpoint): void
|
||||||
|
{
|
||||||
|
self::startSession();
|
||||||
|
|
||||||
|
$counterKey = self::RATE_LIMIT_PREFIX . $endpoint;
|
||||||
|
$timeKey = self::RATE_LIMIT_TIME_PREFIX . $endpoint;
|
||||||
|
|
||||||
|
unset($_SESSION[$counterKey]);
|
||||||
|
unset($_SESSION[$timeKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check limit and throw exception if exceeded
|
||||||
|
* Dies immediately with message if limit is reached
|
||||||
|
*
|
||||||
|
* @param string $endpoint Name of the endpoint
|
||||||
|
* @param int $maxAttempts Maximum attempts allowed
|
||||||
|
* @param int $timeWindow Time window in seconds
|
||||||
|
* @param string $customMessage Optional custom error message
|
||||||
|
* @return void Dies if limit exceeded
|
||||||
|
*/
|
||||||
|
public static function requireLimit(
|
||||||
|
string $endpoint,
|
||||||
|
int $maxAttempts = 5,
|
||||||
|
int $timeWindow = 900,
|
||||||
|
string $customMessage = null
|
||||||
|
): void {
|
||||||
|
if (self::isLimited($endpoint, $maxAttempts, $timeWindow)) {
|
||||||
|
$remaining = self::getTimeRemaining($endpoint, $timeWindow);
|
||||||
|
|
||||||
|
$message = $customMessage ?? sprintf(
|
||||||
|
'Too many attempts. Please try again in %d seconds.',
|
||||||
|
$remaining
|
||||||
|
);
|
||||||
|
|
||||||
|
if (self::isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(429); // Too Many Requests
|
||||||
|
die(json_encode([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => $message,
|
||||||
|
'retry_after' => $remaining
|
||||||
|
]));
|
||||||
|
} else {
|
||||||
|
http_response_code(429);
|
||||||
|
die("<h1>Too Many Requests</h1><p>$message</p>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if request is AJAX
|
||||||
|
*
|
||||||
|
* @return bool True if request is AJAX
|
||||||
|
*/
|
||||||
|
private static function isAjaxRequest(): bool
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
|
||||||
|
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'
|
||||||
|
) || (
|
||||||
|
isset($_SERVER['CONTENT_TYPE']) &&
|
||||||
|
strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start session if not already started
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function startSession(): void
|
||||||
|
{
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get rate limit status for an endpoint
|
||||||
|
* Useful for logging and monitoring
|
||||||
|
*
|
||||||
|
* @param string $endpoint Name of the endpoint
|
||||||
|
* @param int $maxAttempts Maximum attempts allowed
|
||||||
|
* @param int $timeWindow Time window in seconds
|
||||||
|
* @return array Status array with keys: attempts, max_attempts, remaining, time_remaining, limited
|
||||||
|
*/
|
||||||
|
public static function getStatus(
|
||||||
|
string $endpoint,
|
||||||
|
int $maxAttempts = 5,
|
||||||
|
int $timeWindow = 900
|
||||||
|
): array {
|
||||||
|
self::startSession();
|
||||||
|
|
||||||
|
$counterKey = self::RATE_LIMIT_PREFIX . $endpoint;
|
||||||
|
$attempts = $_SESSION[$counterKey] ?? 0;
|
||||||
|
$remaining = self::getRemainingAttempts($endpoint, $maxAttempts, $timeWindow);
|
||||||
|
$timeRemaining = self::getTimeRemaining($endpoint, $timeWindow);
|
||||||
|
$isLimited = self::isLimited($endpoint, $maxAttempts, $timeWindow);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'endpoint' => $endpoint,
|
||||||
|
'attempts' => $attempts,
|
||||||
|
'max_attempts' => $maxAttempts,
|
||||||
|
'remaining' => $remaining,
|
||||||
|
'time_remaining' => $timeRemaining,
|
||||||
|
'limited' => $isLimited
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ require_once("functions.php");
|
|||||||
require_once 'google-client/vendor/autoload.php'; // Add this line for Google Client
|
require_once 'google-client/vendor/autoload.php'; // Add this line for Google Client
|
||||||
|
|
||||||
use Middleware\CsrfMiddleware;
|
use Middleware\CsrfMiddleware;
|
||||||
|
use Middleware\RateLimitMiddleware;
|
||||||
|
use Services\AuthenticationService;
|
||||||
|
|
||||||
// Check if connection is established
|
// Check if connection is established
|
||||||
if (!$conn) {
|
if (!$conn) {
|
||||||
@@ -64,6 +66,10 @@ if (isset($_GET['code'])) {
|
|||||||
$_SESSION['first_name'] = $first_name;
|
$_SESSION['first_name'] = $first_name;
|
||||||
$_SESSION['profile_pic'] = $picture;
|
$_SESSION['profile_pic'] = $picture;
|
||||||
processLegacyMembership($_SESSION['user_id']);
|
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']);
|
// echo json_encode(['status' => 'success', 'message' => 'Google login successful']);
|
||||||
header("Location: index.php");
|
header("Location: index.php");
|
||||||
exit();
|
exit();
|
||||||
@@ -79,6 +85,10 @@ if (isset($_GET['code'])) {
|
|||||||
$_SESSION['first_name'] = $row['first_name'];
|
$_SESSION['first_name'] = $row['first_name'];
|
||||||
$_SESSION['profile_pic'] = $row['profile_pic'];
|
$_SESSION['profile_pic'] = $row['profile_pic'];
|
||||||
sendEmail('chrispintoza@gmail.com', '4WDCSA: New User Login', $name.' has just logged in using Google Login.');
|
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']);
|
// echo json_encode(['status' => 'success', 'message' => 'Google login successful']);
|
||||||
header("Location: index.php");
|
header("Location: index.php");
|
||||||
exit();
|
exit();
|
||||||
@@ -93,6 +103,17 @@ if (isset($_GET['code'])) {
|
|||||||
|
|
||||||
// Check if email and password login is requested
|
// Check if email and password login is requested
|
||||||
if (isset($_POST['email']) && isset($_POST['password'])) {
|
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
|
// Retrieve and sanitize form data
|
||||||
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
|
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
|
||||||
$password = trim($_POST['password']); // Remove extra spaces
|
$password = trim($_POST['password']); // Remove extra spaces
|
||||||
@@ -100,11 +121,13 @@ if (isset($_POST['email']) && isset($_POST['password'])) {
|
|||||||
// Validate input
|
// Validate input
|
||||||
if (empty($email) || empty($password)) {
|
if (empty($email) || empty($password)) {
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Please enter both email and password.']);
|
echo json_encode(['status' => 'error', 'message' => 'Please enter both email and password.']);
|
||||||
|
RateLimitMiddleware::incrementAttempt('login', 900);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']);
|
echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']);
|
||||||
|
RateLimitMiddleware::incrementAttempt('login', 900);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +151,7 @@ if (isset($_POST['email']) && isset($_POST['password'])) {
|
|||||||
// Check if the user is verified
|
// Check if the user is verified
|
||||||
if ($row['is_verified'] == 0) {
|
if ($row['is_verified'] == 0) {
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Your account is not verified. Please check your email for the verification link.']);
|
echo json_encode(['status' => 'error', 'message' => 'Your account is not verified. Please check your email for the verification link.']);
|
||||||
|
RateLimitMiddleware::incrementAttempt('login', 900);
|
||||||
exit();
|
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['user_id'] = $row['user_id']; // Adjust as per your table structure
|
||||||
$_SESSION['first_name'] = $row['first_name']; // Adjust as per your table structure
|
$_SESSION['first_name'] = $row['first_name']; // Adjust as per your table structure
|
||||||
$_SESSION['profile_pic'] = $row['profile_pic'];
|
$_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']);
|
echo json_encode(['status' => 'success', 'message' => 'Successful Login']);
|
||||||
} else {
|
} else {
|
||||||
// Password is incorrect
|
// Password is incorrect - increment rate limit
|
||||||
|
RateLimitMiddleware::incrementAttempt('login', 900);
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Invalid password.']);
|
echo json_encode(['status' => 'error', 'message' => 'Invalid password.']);
|
||||||
}
|
}
|
||||||
} else {
|
} 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.']);
|
echo json_encode(['status' => 'error', 'message' => 'User with that email does not exist.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user