Compare commits
No commits in common. "f5499402492fce8f48c91d3922a0607ce24534de" and "f7e4aeb898462a9902fa7ae5ba3666eb022f144d" have entirely different histories.
f549940249
...
f7e4aeb898
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,15 +88,6 @@ 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();
|
||||||
|
@ -108,15 +97,8 @@ class User {
|
||||||
$_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.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in New Issue