Compare commits

..

No commits in common. "f5499402492fce8f48c91d3922a0607ce24534de" and "f7e4aeb898462a9902fa7ae5ba3666eb022f144d" have entirely different histories.

3 changed files with 43 additions and 156 deletions

View File

@ -1,91 +0,0 @@
<?php
class RateLimiter {
private $db;
private $maxAttempts = 5; // Maximum login attempts
private $decayMinutes = 15; // Time window in minutes
private $tableName = 'login_attempts';
public function __construct($database) {
$this->db = $database->getConnection();
$this->createTableIfNotExists();
}
private function createTableIfNotExists() {
$sql = "CREATE TABLE IF NOT EXISTS {$this->tableName} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip_address VARCHAR(45) NOT NULL,
username VARCHAR(255) NOT NULL,
attempted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_ip_username (ip_address, username)
)";
$this->db->exec($sql);
}
public function attempt($username, $ipAddress) {
// Clean old attempts
$this->clearOldAttempts();
// Record this attempt
$sql = "INSERT INTO {$this->tableName} (ip_address, username) VALUES (:ip, :username)";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':ip' => $ipAddress,
':username' => $username
]);
// Check if too many attempts
return !$this->tooManyAttempts($username, $ipAddress);
}
public function tooManyAttempts($username, $ipAddress) {
$sql = "SELECT COUNT(*) as attempts
FROM {$this->tableName}
WHERE ip_address = :ip
AND username = :username
AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':ip' => $ipAddress,
':username' => $username,
':minutes' => $this->decayMinutes
]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result['attempts'] >= $this->maxAttempts;
}
public function clearOldAttempts() {
$sql = "DELETE FROM {$this->tableName}
WHERE attempted_at < datetime('now', '-' || :minutes || ' minutes')";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':minutes' => $this->decayMinutes
]);
}
public function getRemainingAttempts($username, $ipAddress) {
$sql = "SELECT COUNT(*) as attempts
FROM {$this->tableName}
WHERE ip_address = :ip
AND username = :username
AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':ip' => $ipAddress,
':username' => $username,
':minutes' => $this->decayMinutes
]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return max(0, $this->maxAttempts - $result['attempts']);
}
public function getDecayMinutes() {
return $this->decayMinutes;
}
}

View File

@ -10,7 +10,6 @@ class User {
* @var PDO|null $db The database connection instance. * @var PDO|null $db The database connection instance.
*/ */
private $db; private $db;
private $ratelimiter;
/** /**
* User constructor. * User constructor.
@ -20,7 +19,6 @@ class User {
*/ */
public function __construct($database) { public function __construct($database) {
$this->db = $database->getConnection(); $this->db = $database->getConnection();
$this->ratelimiter = new RateLimiter($database);
} }
@ -90,33 +88,17 @@ class User {
* @return bool True if login is successful, false otherwise. * @return bool True if login is successful, false otherwise.
*/ */
public function login($username, $password) { public function login($username, $password) {
// get client IP address
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
// check rate limiting
if (!$this->rateLimiter->attempt($username, $ipAddress)) {
$remainingTime = $this->rateLimiter->getDecayMinutes();
throw new Exception("Too many login attempts. Please try again in {$remainingTime} minutes.");
}
$query = $this->db->prepare("SELECT * FROM users WHERE username = :username"); $query = $this->db->prepare("SELECT * FROM users WHERE username = :username");
$query->bindParam(':username', $username); $query->bindParam(':username', $username);
$query->execute(); $query->execute();
$user = $query->fetch(PDO::FETCH_ASSOC); $user = $query->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) { if ( $user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id']; $_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username']; $_SESSION['username'] = $user['username'];
return true; return true;
}
// Login failed, return remaining attempts info
$remainingAttempts = $this->rateLimiter->getRemainingAttempts($username, $ipAddress);
if ($remainingAttempts > 0) {
throw new Exception("Invalid credentials. {$remainingAttempts} attempts remaining.");
} else { } else {
$remainingTime = $this->rateLimiter->getDecayMinutes(); return false;
throw new Exception("Too many login attempts. Please try again in {$remainingTime} minutes.");
} }
} }

View File

@ -22,7 +22,6 @@ try {
$dbWeb = connectDB($config); $dbWeb = connectDB($config);
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) { if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
try {
$username = $_POST['username']; $username = $_POST['username'];
$password = $_POST['password']; $password = $_POST['password'];
@ -67,9 +66,6 @@ try {
header('Location: ' . htmlspecialchars($app_root)); header('Location: ' . htmlspecialchars($app_root));
exit(); exit();
} }
} catch (Exception $e) {
$error = $e->getMessage();
}
} }
} catch (Exception $e) { } catch (Exception $e) {
$error = getError('There was an unexpected error. Please try again.', $e->getMessage()); $error = getError('There was an unexpected error. Please try again.', $e->getMessage());