Adds a web interface for ratelimiter

main
Yasen Pramatarov 2025-01-04 12:30:44 +02:00
parent 84354b183d
commit 9c129fcf76
6 changed files with 345 additions and 21 deletions

View File

@ -1,15 +1,15 @@
<?php <?php
class RateLimiter { class RateLimiter {
private $db; public $db;
private $log; private $log;
private $maxAttempts = 5; // Maximum login attempts public $maxAttempts = 5; // Maximum login attempts
private $decayMinutes = 15; // Time window in minutes public $decayMinutes = 15; // Time window in minutes
private $autoBlacklistThreshold = 10; // Attempts before auto-blacklist public $autoBlacklistThreshold = 10; // Attempts before auto-blacklist
private $autoBlacklistDuration = 24; // Hours to blacklist for public $autoBlacklistDuration = 24; // Hours to blacklist for
private $ratelimitTable = 'login_attempts'; public $ratelimitTable = 'login_attempts';
private $whitelistTable = 'ip_whitelist'; public $whitelistTable = 'ip_whitelist';
private $blacklistTable = 'ip_blacklist'; public $blacklistTable = 'ip_blacklist';
public function __construct($database) { public function __construct($database) {
$this->db = $database->getConnection(); $this->db = $database->getConnection();

View File

@ -0,0 +1,63 @@
<?php
// Check if user has any of the required rights
if (!($userObject->hasRight($user_id, 'superuser') ||
$userObject->hasRight($user_id, 'edit whitelist') ||
$userObject->hasRight($user_id, 'edit blacklist') ||
$userObject->hasRight($user_id, 'edit ratelimiting'))) {
include '../app/templates/error-unauthorized.php';
exit;
}
$action = $_GET['action'] ?? 'view';
$section = $_GET['section'] ?? 'whitelist';
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
switch ($_POST['action']) {
case 'add_whitelist':
if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) {
$ip = $_POST['ip_address'];
$description = $_POST['description'];
$is_network = isset($_POST['is_network']) ? 1 : 0;
$rateLimiter->addToWhitelist($ip, $is_network, $description, $currentUser);
}
break;
case 'remove_whitelist':
if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) {
$ip = $_POST['ip_address'];
$rateLimiter->removeFromWhitelist($ip, $user_id, $currentUser);
}
break;
case 'add_blacklist':
if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist')) {
$ip = $_POST['ip_address'];
$reason = $_POST['reason'];
$is_network = isset($_POST['is_network']) ? 1 : 0;
$expiry_hours = empty($_POST['expiry_hours']) ? null : intval($_POST['expiry_hours']);
$rateLimiter->addToBlacklist($ip, $is_network, $reason, $currentUser, null, $expiry_hours);
}
break;
case 'remove_blacklist':
if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist')) {
$ip = $_POST['ip_address'];
$rateLimiter->removeFromBlacklist($ip, $user_id, $currentUser);
}
break;
}
// Redirect to prevent form resubmission
header("Location: {$app_root}?page=security&section={$section}");
exit;
}
// Get the lists
$whitelisted = $rateLimiter->getWhitelistedIps();
$blacklisted = $rateLimiter->getBlacklistedIps();
// Include the template
include '../app/templates/security.php';
?>

View File

@ -92,12 +92,23 @@ $timeNow = new DateTime('now', new DateTimeZone($userTimezone));
<i class="fas fa-wrench" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>config file <i class="fas fa-wrench" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="configuration"></i>config file
</li> </li>
</a> </a>
<?php } ?>
<?php if ($userObject->hasRight($user_id, 'superuser') ||
$userObject->hasRight($user_id, 'edit whitelist') ||
$userObject->hasRight($user_id, 'edit blacklist') ||
$userObject->hasRight($user_id, 'edit ratelimiting')) { ?>
<a href="<?= htmlspecialchars($app_root) ?>?page=security">
<li class="list-group-item<?php if ($page === 'security') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-shield-alt" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="security"></i>security
</li>
</a>
<?php } ?> <?php } ?>
<a href="<?= htmlspecialchars($app_root) ?>?page=status"> <a href="<?= htmlspecialchars($app_root) ?>?page=status">
<li class="list-group-item<?php if ($page === 'status' && $item === '') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>"> <li class="list-group-item<?php if ($page === 'status' && $item === '') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">
<i class="fas fa-heartbeat" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="status"></i>status <i class="fas fa-heartbeat" data-toggle="tooltip" data-placement="right" data-offset="30.0" title="status"></i>status
</li> </li>
</a> </a>
<?php if ($userObject->hasRight($user_id, 'view app logs')) {?> <?php if ($userObject->hasRight($user_id, 'view app logs')) {?>
<a href="<?= htmlspecialchars($app_root) ?>?page=logs"> <a href="<?= htmlspecialchars($app_root) ?>?page=logs">
<li class="list-group-item<?php if ($page === 'logs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>"> <li class="list-group-item<?php if ($page === 'logs') echo ' list-group-item-secondary'; else echo ' list-group-item-action'; ?>">

View File

@ -0,0 +1,223 @@
<!-- Security Settings -->
<div class="container">
<div class="row mb-4">
<div class="col">
<h2>Security Settings</h2>
<ul class="nav nav-tabs">
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) { ?>
<li class="nav-item">
<a class="nav-link <?= $section === 'whitelist' ? 'active' : '' ?>" href="?page=security&section=whitelist">IP Whitelist</a>
</li>
<?php } ?>
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist')) { ?>
<li class="nav-item">
<a class="nav-link <?= $section === 'blacklist' ? 'active' : '' ?>" href="?page=security&section=blacklist">IP Blacklist</a>
</li>
<?php } ?>
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting')) { ?>
<li class="nav-item">
<a class="nav-link <?= $section === 'ratelimit' ? 'active' : '' ?>" href="?page=security&section=ratelimit">Rate Limiting</a>
</li>
<?php } ?>
</ul>
</div>
</div>
<?php if ($section === 'whitelist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist'))) { ?>
<!-- Whitelist Section -->
<div class="row mb-4">
<div class="col">
<div class="card">
<div class="card-header">
<h3>IP Whitelist</h3>
IP addresses and networks that will always bypass the ratelimiting login checks.
</div>
<div class="card-body">
<form method="POST" class="mb-4">
<input type="hidden" name="action" value="add_whitelist">
<div class="row g-3">
<div class="col-md-4">
<input type="text" class="form-control" name="ip_address" placeholder="IP Address or CIDR" required>
</div>
<div class="col-md-4">
<input type="text" class="form-control" name="description" placeholder="Description">
</div>
<div class="col-md-2">
<div class="form-check">
<input type="checkbox" class="form-check-input" name="is_network" id="is_network_white">
<label class="form-check-label" for="is_network_white">Is Network</label>
</div>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Add to Whitelist</button>
</div>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>IP Address</th>
<th>Network</th>
<th>Description</th>
<th>Added By</th>
<th>Added On</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($whitelisted as $ip) { ?>
<tr>
<td><?= htmlspecialchars($ip['ip_address']) ?></td>
<td><?= $ip['is_network'] ? 'Yes' : 'No' ?></td>
<td><?= htmlspecialchars($ip['description']) ?></td>
<td><?= htmlspecialchars($ip['created_by']) ?></td>
<td><?= htmlspecialchars($ip['created_at']) ?></td>
<td>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="remove_whitelist">
<input type="hidden" name="ip_address" value="<?= htmlspecialchars($ip['ip_address']) ?>">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this IP from whitelist?')">Remove</button>
</form>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php } ?>
<?php if ($section === 'blacklist' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit blacklist'))) { ?>
<!-- Blacklist Section -->
<div class="row mb-4">
<div class="col">
<div class="card">
<div class="card-header">
<h3>IP Blacklist</h3>
IP addresses and networks that will always get blocked at login.
</div>
<div class="card-body">
<form method="POST" class="mb-4">
<input type="hidden" name="action" value="add_blacklist">
<div class="row g-3">
<div class="col-md-3">
<input type="text" class="form-control" name="ip_address" placeholder="IP Address or CIDR" required>
</div>
<div class="col-md-3">
<input type="text" class="form-control" name="reason" placeholder="Reason">
</div>
<div class="col-md-2">
<input type="number" class="form-control" name="expiry_hours" placeholder="Expiry (hours)">
</div>
<div class="col-md-2">
<div class="form-check">
<input type="checkbox" class="form-check-input" name="is_network" id="is_network_black">
<label class="form-check-label" for="is_network_black">Is Network</label>
</div>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Add to Blacklist</button>
</div>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>IP Address</th>
<th>Network</th>
<th>Reason</th>
<th>Added By</th>
<th>Added On</th>
<th>Expires</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($blacklisted as $ip) { ?>
<tr>
<td><?= htmlspecialchars($ip['ip_address']) ?></td>
<td><?= $ip['is_network'] ? 'Yes' : 'No' ?></td>
<td><?= htmlspecialchars($ip['reason']) ?></td>
<td><?= htmlspecialchars($ip['created_by']) ?></td>
<td><?= htmlspecialchars($ip['created_at']) ?></td>
<td><?= $ip['expiry_time'] ? htmlspecialchars($ip['expiry_time']) : 'Never' ?></td>
<td>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="remove_blacklist">
<input type="hidden" name="ip_address" value="<?= htmlspecialchars($ip['ip_address']) ?>">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this IP from blacklist?')">Remove</button>
</form>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php } ?>
<?php if ($section === 'ratelimit' && ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit ratelimiting'))) { ?>
<!-- Rate Limiting Section -->
<div class="row mb-4">
<div class="col">
<div class="card">
<div class="card-header">
<h3>Rate Limiting Settings</h3>
Restricts brute force or flooding attempts at login page.
</div>
<div class="card-body">
<div class="alert alert-info">
<h4>Current Settings</h4>
<ul>
<li>Maximum login attempts: <?= $rateLimiter->maxAttempts ?></li>
<li>Time window: <?= $rateLimiter->decayMinutes ?> minutes</li>
<li>Auto-blacklist threshold: <?= $rateLimiter->autoBlacklistThreshold ?> attempts</li>
<li>Auto-blacklist duration: <?= $rateLimiter->autoBlacklistDuration ?> hours</li>
</ul>
<p class="mb-0">
<small>Note: These settings can be modified in the RateLimiter class configuration.</small>
</p>
</div>
<h4>Recent Failed Login Attempts</h4>
<table class="table">
<thead>
<tr>
<th>IP Address</th>
<th>Username</th>
<th>Attempted At</th>
</tr>
</thead>
<tbody>
<?php
$stmt = $rateLimiter->db->prepare("
SELECT ip_address, username, attempted_at
FROM {$rateLimiter->ratelimitTable}
ORDER BY attempted_at DESC
LIMIT 10
");
$stmt->execute();
$attempts = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($attempts as $attempt) {
?>
<tr>
<td><?= htmlspecialchars($attempt['ip_address']) ?></td>
<td><?= htmlspecialchars($attempt['username']) ?></td>
<td><?= htmlspecialchars($attempt['attempted_at']) ?></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php } ?>
</div>
<!-- /Security Settings -->

View File

@ -1,13 +1,16 @@
INSERT OR IGNORE INTO rights VALUES(1,'superuser'); INSERT OR IGNORE INTO rights (`id`, `name`) VALUES(1,'superuser');
INSERT OR IGNORE INTO rights VALUES(2,'edit users'); INSERT OR IGNORE INTO rights (`name`) VALUES('edit users');
INSERT OR IGNORE INTO rights VALUES(3,'view config file'); INSERT OR IGNORE INTO rights (`name`) VALUES('view config file');
INSERT OR IGNORE INTO rights VALUES(4,'edit config file'); INSERT OR IGNORE INTO rights (`name`) VALUES('edit config file');
INSERT OR IGNORE INTO rights VALUES(5,'view own profile'); INSERT OR IGNORE INTO rights (`name`) VALUES('view own profile');
INSERT OR IGNORE INTO rights VALUES(6,'edit own profile'); INSERT OR IGNORE INTO rights (`name`) VALUES('edit own profile');
INSERT OR IGNORE INTO rights VALUES(7,'view all profiles'); INSERT OR IGNORE INTO rights (`name`) VALUES('view all profiles');
INSERT OR IGNORE INTO rights VALUES(8,'edit all profiles'); INSERT OR IGNORE INTO rights (`name`) VALUES('edit all profiles');
INSERT OR IGNORE INTO rights VALUES(9,'view app logs'); INSERT OR IGNORE INTO rights (`name`) VALUES('view app logs');
INSERT OR IGNORE INTO rights VALUES(10,'view all platforms'); INSERT OR IGNORE INTO rights (`name`) VALUES('view all platforms');
INSERT OR IGNORE INTO rights VALUES(11,'edit all platforms'); INSERT OR IGNORE INTO rights (`name`) VALUES('edit all platforms');
INSERT OR IGNORE INTO rights VALUES(12,'view all agents'); INSERT OR IGNORE INTO rights (`name`) VALUES('view all agents');
INSERT OR IGNORE INTO rights VALUES(13,'edit all agents'); INSERT OR IGNORE INTO rights (`name`) VALUES('edit all agents');
INSERT OR IGNORE INTO rights (`name`) VALUES('edit whitelist');
INSERT OR IGNORE INTO rights (`name`) VALUES('edit blacklist');
INSERT OR IGNORE INTO rights (`name`) VALUES('edit ratelimiting');

View File

@ -44,6 +44,7 @@ $allowed_urls = [
'config', 'config',
'status', 'status',
'logs', 'logs',
'security',
'help', 'help',
'login', 'login',
@ -148,6 +149,29 @@ if ($page == 'logout') {
include '../app/templates/block-message.php'; include '../app/templates/block-message.php';
include '../app/pages/login.php'; include '../app/pages/login.php';
} elseif ($page === 'security') {
// Security settings require login
if (!isset($currentUser)) {
include '../app/templates/error-unauthorized.php';
exit;
}
// Get user details and rights
$user_id = $userObject->getUserId($currentUser)[0]['id'];
$userDetails = $userObject->getUserDetails($user_id);
$userRights = $userObject->getUserRights($user_id);
$userTimezone = isset($userDetails[0]['timezone']) ? $userDetails[0]['timezone'] : 'UTC';
// Initialize RateLimiter
require_once '../app/classes/ratelimiter.php';
$rateLimiter = new RateLimiter($dbWeb);
include '../app/templates/page-header.php';
include '../app/templates/page-menu.php';
include '../app/templates/page-sidebar.php';
include '../app/pages/security.php';
include '../app/templates/page-footer.php';
} else { } else {
// if user is logged in, we need user details and rights // if user is logged in, we need user details and rights