Implements and troubleshoots new messages system

main
Yasen Pramatarov 2025-01-06 11:13:28 +02:00
parent 4bfae911db
commit b314cdd14d
15 changed files with 343 additions and 233 deletions

View File

@ -7,106 +7,136 @@ class Messages {
const TYPE_INFO = 'info';
const TYPE_WARNING = 'warning';
// Message categories
const SECURITY = [
'WHITELIST_ADD_SUCCESS' => [
'message' => 'IP address successfully added to whitelist.',
'type' => self::TYPE_SUCCESS,
'dismissible' => true
],
'WHITELIST_ADD_ERROR' => [
'message' => 'Failed to add IP to whitelist. Please check the IP format.',
'type' => self::TYPE_ERROR,
'dismissible' => true
],
'WHITELIST_REMOVE_SUCCESS' => [
'message' => 'IP address successfully removed from whitelist.',
'type' => self::TYPE_SUCCESS,
'dismissible' => true
],
'WHITELIST_REMOVE_ERROR' => [
'message' => 'Failed to remove IP from whitelist.',
'type' => self::TYPE_ERROR,
'dismissible' => true
],
'BLACKLIST_ADD_SUCCESS' => [
'message' => 'IP address successfully added to blacklist.',
'type' => self::TYPE_SUCCESS,
'dismissible' => true
],
'BLACKLIST_ADD_ERROR' => [
'message' => 'Failed to add IP to blacklist. Please check the IP format.',
'type' => self::TYPE_ERROR,
'dismissible' => true
],
'BLACKLIST_REMOVE_SUCCESS' => [
'message' => 'IP address successfully removed from blacklist.',
'type' => self::TYPE_SUCCESS,
'dismissible' => true
],
'BLACKLIST_REMOVE_ERROR' => [
'message' => 'Failed to remove IP from blacklist.',
'type' => self::TYPE_ERROR,
'dismissible' => true
],
'IP_REQUIRED' => [
'message' => 'IP address is required.',
'type' => self::TYPE_ERROR,
'dismissible' => true
],
'PERMISSION_DENIED' => [
'message' => 'You do not have permission to perform this action.',
'type' => self::TYPE_ERROR,
'dismissible' => false
],
'RATE_LIMIT_INFO' => [
'message' => 'Rate limiting is active. This helps protect against brute force attacks.',
// Default message configurations
const NOTICE = [
'DEFAULT' => [
'type' => self::TYPE_INFO,
'dismissible' => true
]
];
const ERROR = [
'DEFAULT' => [
'type' => self::TYPE_ERROR,
'dismissible' => false
]
];
const LOGIN = [
'LOGIN_FAILED' => [
'message' => 'Invalid username or password.',
'type' => self::TYPE_ERROR,
'LOGIN_SUCCESS' => [
'type' => self::TYPE_SUCCESS,
'dismissible' => true
],
'LOGIN_BLOCKED' => [
'message' => 'Too many failed attempts. Please try again later.',
'LOGIN_FAILED' => [
'type' => self::TYPE_ERROR,
'dismissible' => false
],
'LOGOUT_SUCCESS' => [
'type' => self::TYPE_SUCCESS,
'dismissible' => true
],
'IP_BLACKLISTED' => [
'message' => 'Access denied. Your IP address is blacklisted.',
'type' => self::TYPE_ERROR,
'dismissible' => false
],
'IP_NOT_WHITELISTED' => [
'type' => self::TYPE_ERROR,
'dismissible' => false
],
'TOO_MANY_ATTEMPTS' => [
'type' => self::TYPE_ERROR,
'dismissible' => false
]
];
const SECURITY = [
'WHITELIST_ADD_SUCCESS' => [
'type' => self::TYPE_SUCCESS,
'dismissible' => true
],
'WHITELIST_ADD_ERROR' => [
'type' => self::TYPE_ERROR,
'dismissible' => true
],
'WHITELIST_REMOVE_SUCCESS' => [
'type' => self::TYPE_SUCCESS,
'dismissible' => true
],
'WHITELIST_REMOVE_ERROR' => [
'type' => self::TYPE_ERROR,
'dismissible' => true
],
'BLACKLIST_ADD_SUCCESS' => [
'type' => self::TYPE_SUCCESS,
'dismissible' => true
],
'BLACKLIST_ADD_ERROR' => [
'type' => self::TYPE_ERROR,
'dismissible' => true
],
'BLACKLIST_REMOVE_SUCCESS' => [
'type' => self::TYPE_SUCCESS,
'dismissible' => true
],
'BLACKLIST_REMOVE_ERROR' => [
'type' => self::TYPE_ERROR,
'dismissible' => true
],
'RATE_LIMIT_INFO' => [
'type' => self::TYPE_INFO,
'dismissible' => false
],
'PERMISSION_DENIED' => [
'type' => self::TYPE_ERROR,
'dismissible' => false
],
'IP_REQUIRED' => [
'type' => self::TYPE_ERROR,
'dismissible' => false
]
];
private static $strings = null;
/**
* Get message strings
*/
private static function getStrings() {
if (self::$strings === null) {
self::$strings = require __DIR__ . '/../includes/messages-strings.php';
}
return self::$strings;
}
/**
* Get message configuration by key
*/
public static function get($category, $key) {
$messages = constant("self::$category");
return $messages[$key] ?? null;
$config = constant("self::$category")[$key] ?? null;
if (!$config) return null;
$strings = self::getStrings();
$message = $strings[$category][$key] ?? '';
return array_merge($config, ['message' => $message]);
}
/**
* Render message HTML
*/
public static function render($category, $key, $customMessage = null) {
public static function render($category, $key, $customMessage = null, $dismissible = null) {
$config = self::get($category, $key);
if (!$config) return '';
$message = $customMessage ?? $config['message'];
$dismissible = $config['dismissible'] ? ' alert-dismissible fade show' : '';
$dismissButton = $config['dismissible'] ? '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' : '';
$isDismissible = $dismissible ?? $config['dismissible'];
$dismissClass = $isDismissible ? ' alert-dismissible fade show' : '';
$dismissButton = $isDismissible ? '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' : '';
return sprintf(
'<div class="alert alert-%s%s" role="alert">%s%s</div>',
$config['type'],
$dismissible,
$dismissClass,
htmlspecialchars($message),
$dismissButton
);
@ -115,14 +145,15 @@ class Messages {
/**
* Store message in session for display after redirect
*/
public static function flash($category, $key, $customMessage = null) {
public static function flash($category, $key, $customMessage = null, $dismissible = null) {
if (!isset($_SESSION['flash_messages'])) {
$_SESSION['flash_messages'] = [];
}
$_SESSION['flash_messages'][] = [
'category' => $category,
'key' => $key,
'custom_message' => $customMessage
'custom_message' => $customMessage,
'dismissible' => $dismissible
];
}

View File

@ -22,7 +22,7 @@ class RateLimiter {
// Login attempts table
$sql = "CREATE TABLE IF NOT EXISTS {$this->ratelimitTable} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip_address TEXT NOT NULL,
ip_address TEXT NOT NULL UNIQUE,
username TEXT NOT NULL,
attempted_at TEXT DEFAULT (DATETIME('now'))
)";
@ -53,11 +53,11 @@ class RateLimiter {
// Default IPs to whitelist (local interface and private networks IPs)
$defaultIps = [
['127.0.0.1', 0, 'localhost IPv4'],
['::1', 0, 'localhost IPv6'],
['10.0.0.0/8', 1, 'Private network (Class A)'],
['172.16.0.0/12', 1, 'Private network (Class B)'],
['192.168.0.0/16', 1, 'Private network (Class C)']
['127.0.0.1', false, 'localhost IPv4'],
['::1', false, 'localhost IPv6'],
['10.0.0.0/8', true, 'Private network (Class A)'],
['172.16.0.0/12', true, 'Private network (Class B)'],
['192.168.0.0/16', true, 'Private network (Class C)']
];
// Insert default whitelisted IPs if they don't exist
@ -70,11 +70,11 @@ class RateLimiter {
// Insert known malicious networks
$defaultBlacklist = [
['0.0.0.0/8', 1, 'Reserved address space - RFC 1122'],
['100.64.0.0/10', 1, 'Carrier-grade NAT space - RFC 6598'],
['192.0.2.0/24', 1, 'TEST-NET-1 Documentation space - RFC 5737'],
['198.51.100.0/24', 1, 'TEST-NET-2 Documentation space - RFC 5737'],
['203.0.113.0/24', 1, 'TEST-NET-3 Documentation space - RFC 5737']
['0.0.0.0/8', true, 'Reserved address space - RFC 1122'],
['100.64.0.0/10', true, 'Carrier-grade NAT space - RFC 6598'],
['192.0.2.0/24', true, 'TEST-NET-1 Documentation space - RFC 5737'],
['198.51.100.0/24', true, 'TEST-NET-2 Documentation space - RFC 5737'],
['203.0.113.0/24', true, 'TEST-NET-3 Documentation space - RFC 5737']
];
$stmt = $this->db->prepare("INSERT OR IGNORE INTO {$this->blacklistTable}
@ -87,12 +87,31 @@ class RateLimiter {
}
private function isIpWhitelisted($ip) {
// Check exact IP match and CIDR ranges
$stmt = $this->db->prepare("SELECT ip_address, is_network FROM {$this->whitelistTable}");
/**
* Get number of recent login attempts for an IP
*/
public function getRecentAttempts($ip) {
$stmt = $this->db->prepare("SELECT COUNT(*) as attempts FROM {$this->ratelimitTable}
WHERE ip_address = ? AND attempted_at > datetime('now', '-' || :minutes || ' minutes')");
$stmt->execute([$ip, $this->decayMinutes]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return intval($result['attempts']);
}
/**
* Check if an IP is blacklisted
*/
public function isIpBlacklisted($ip) {
// First check if IP is explicitly blacklisted or in a blacklisted range
$stmt = $this->db->prepare("SELECT ip_address, is_network, expiry_time FROM {$this->blacklistTable}");
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// Skip expired entries
if ($row['expiry_time'] !== null && strtotime($row['expiry_time']) < time()) {
continue;
}
if ($row['is_network']) {
if ($this->ipInRange($ip, $row['ip_address'])) {
return true;
@ -107,17 +126,15 @@ class RateLimiter {
return false;
}
private function isIpBlacklisted($ip) {
// First check if IP is explicitly blacklisted or in a blacklisted range
$stmt = $this->db->prepare("SELECT ip_address, is_network, expiry_time FROM {$this->blacklistTable}");
/**
* Check if an IP is whitelisted
*/
public function isIpWhitelisted($ip) {
// Check exact IP match and CIDR ranges
$stmt = $this->db->prepare("SELECT ip_address, is_network FROM {$this->whitelistTable}");
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// Skip expired entries
if ($row['expiry_time'] !== null && strtotime($row['expiry_time']) < time()) {
continue;
}
if ($row['is_network']) {
if ($this->ipInRange($ip, $row['ip_address'])) {
return true;
@ -151,6 +168,7 @@ class RateLimiter {
$message = "Cannot whitelist {$ip} - IP is currently blacklisted";
if ($userId) {
$this->log->insertLog($userId, "IP Whitelist: {$message}", 'system');
Messages::flash('ERROR', 'DEFAULT', $message);
}
return false;
}
@ -177,6 +195,7 @@ class RateLimiter {
} catch (Exception $e) {
if ($userId) {
$this->log->insertLog($userId, "IP Whitelist: Failed to add {$ip}: " . $e->getMessage(), 'system');
Messages::flash('ERROR', 'DEFAULT', "IP Whitelist: Failed to add {$ip}: " . $e->getMessage());
}
return false;
}
@ -211,6 +230,7 @@ class RateLimiter {
} catch (Exception $e) {
if ($userId) {
$this->log->insertLog($userId, "IP Whitelist: Failed to remove {$ip}: " . $e->getMessage(), 'system');
Messages::flash('ERROR', 'DEFAULT', "IP Whitelist: Failed to remove {$ip}: " . $e->getMessage());
}
return false;
}
@ -223,6 +243,7 @@ class RateLimiter {
$message = "Cannot blacklist {$ip} - IP is currently whitelisted";
if ($userId) {
$this->log->insertLog($userId, "IP Blacklist: {$message}", 'system');
Messages::flash('ERROR', 'DEFAULT', $message);
}
return false;
}
@ -251,6 +272,7 @@ class RateLimiter {
} catch (Exception $e) {
if ($userId) {
$this->log->insertLog($userId, "IP Blacklist: Failed to add {$ip}: " . $e->getMessage(), 'system');
Messages::flash('ERROR', 'DEFAULT', "IP Blacklist: Failed to add {$ip}: " . $e->getMessage());
}
return false;
}
@ -283,6 +305,7 @@ class RateLimiter {
} catch (Exception $e) {
if ($userId) {
$this->log->insertLog($userId, "IP Blacklist: Failed to remove {$ip}: " . $e->getMessage(), 'system');
Messages::flash('ERROR', 'DEFAULT', "IP Blacklist: Failed to remove {$ip}: " . $e->getMessage());
}
return false;
}
@ -317,6 +340,7 @@ class RateLimiter {
return true;
} catch (Exception $e) {
$this->log->insertLog(0, "Failed to cleanup expired entries: " . $e->getMessage(), 'system');
Messages::flash('ERROR', 'DEFAULT', "Failed to cleanup expired entries: " . $e->getMessage());
return false;
}
}

View File

@ -0,0 +1,9 @@
<?php
if (isset($messages) && is_array($messages)) {
foreach ($messages as $msg) {
echo Messages::render($msg['category'], $msg['key'], $msg['custom_message'] ?? null);
}
}
?>

View File

@ -0,0 +1,36 @@
<?php
// Message strings for translation
return [
'LOGIN' => [
'LOGIN_SUCCESS' => 'Login successful.',
'LOGIN_FAILED' => 'Login failed. Please check your credentials.',
'LOGOUT_SUCCESS' => 'Logout successful. You can log in again.',
'IP_BLACKLISTED' => 'Access denied. Your IP address is blacklisted.',
'IP_NOT_WHITELISTED' => 'Access denied. Your IP address is not whitelisted.',
'TOO_MANY_ATTEMPTS' => 'Too many login attempts. Please try again later.',
],
'SECURITY' => [
'WHITELIST_ADD_SUCCESS' => 'IP address successfully added to whitelist.',
'WHITELIST_ADD_ERROR' => 'Failed to add IP to whitelist. Please check the IP format.',
'WHITELIST_REMOVE_SUCCESS' => 'IP address successfully removed from whitelist.',
'WHITELIST_REMOVE_ERROR' => 'Failed to remove IP from whitelist.',
'BLACKLIST_ADD_SUCCESS' => 'IP address successfully added to blacklist.',
'BLACKLIST_ADD_ERROR' => 'Failed to add IP to blacklist. Please check the IP format.',
'BLACKLIST_REMOVE_SUCCESS' => 'IP address successfully removed from blacklist.',
'BLACKLIST_REMOVE_ERROR' => 'Failed to remove IP from blacklist.',
'RATE_LIMIT_INFO' => 'Rate limiting is active. This helps protect against brute force attacks.',
'PERMISSION_DENIED' => 'Permission denied. You do not have the required rights.',
'IP_REQUIRED' => 'IP address is required.',
],
'REGISTER' => [
'SUCCESS' => 'Registration successful. You can log in now.',
'FAILED' => 'Registration failed: %s',
'DISABLED' => 'Registration is disabled.',
],
'SYSTEM' => [
'DB_ERROR' => 'Error connecting to the database: %s',
'DB_CONNECT_ERROR' => 'Error connecting to DB: %s',
'DB_UNKNOWN_TYPE' => 'Error: unknown database type "%s"',
],
];

View File

@ -1,17 +1,15 @@
<?php
// Start session if not already started
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Include Messages class
require_once __DIR__ . '/../classes/messages.php';
// Initialize messages array
$messages = [];
// Get any flash messages from previous requests
// Get any flash messages from previous request
$flash_messages = Messages::getFlash();
if (!empty($flash_messages)) {
$messages = array_merge($messages, $flash_messages);
$messages = array_merge($messages, array_map(function($flash) {
return [
'category' => $flash['category'],
'key' => $flash['key'],
'custom_message' => $flash['custom_message']
];
}, $flash_messages));
}
?>

View File

@ -21,6 +21,15 @@ if (isset($_REQUEST['until_time'])) {
$until_time = htmlspecialchars($_REQUEST['until_time']);
}
// sanitize session vars
if (isset($_SESSION)) {
foreach ($_SESSION as $key => $value) {
if (is_string($value)) {
$_SESSION[$key] = htmlspecialchars($value);
}
}
}
// hosts
if (isset($_POST['address'])) {
$address = htmlspecialchars($_POST['address']);

View File

@ -9,6 +9,10 @@
* 3. The most recent 10 conferences.
*/
// Get any new messages
include '../app/includes/messages.php';
include '../app/includes/messages-show.php';
require '../app/classes/conference.php';
require '../app/classes/participant.php';

View File

@ -21,11 +21,28 @@ try {
// connect to database
$dbWeb = connectDB($config);
// Initialize RateLimiter
require_once '../app/classes/ratelimiter.php';
$rateLimiter = new RateLimiter($dbWeb);
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
try {
$username = $_POST['username'];
$password = $_POST['password'];
// Check if IP is blacklisted
if ($rateLimiter->isIpBlacklisted($user_IP)) {
throw new Exception(Messages::get('LOGIN', 'IP_BLACKLISTED')['message']);
}
// Check rate limiting (but skip if IP is whitelisted)
if (!$rateLimiter->isIpWhitelisted($user_IP)) {
$attempts = $rateLimiter->getRecentAttempts($user_IP);
if ($attempts >= $rateLimiter->maxAttempts) {
throw new Exception(Messages::get('LOGIN', 'LOGIN_BLOCKED')['message']);
}
}
// login successful
if ( $userObject->login($username, $password) ) {
// if remember_me is checked, max out the session
@ -52,32 +69,40 @@ try {
'samesite' => 'Strict'
]);
// redirect to index
$_SESSION['notice'] = "Login successful";
// Log successful login
$user_id = $userObject->getUserId($username)[0]['id'];
$logObject->insertLog($user_id, "Login: User \"$username\" logged in. IP: $user_IP", 'user');
// Set success message and redirect
Messages::flash('LOGIN', 'LOGIN_SUCCESS');
header('Location: ' . htmlspecialchars($app_root));
exit();
} else {
throw new Exception(Messages::get('LOGIN', 'LOGIN_FAILED')['message']);
}
} catch (Exception $e) {
// Log the failed attempt
$error = $e->getMessage();
Messages::flash('ERROR', 'DEFAULT', $e->getMessage());
if (isset($username)) {
$user_id = $userObject->getUserId($username)[0]['id'] ?? 0;
$logObject->insertLog($user_id, "Login: Failed login attempt for user \"$username\". IP: $user_IP. Reason: {$error}", 'user');
$logObject->insertLog($user_id, "Login: Failed login attempt for user \"$username\". IP: $user_IP. Reason: {$e->getMessage()}", 'user');
}
include '../app/templates/block-message.php';
}
}
} catch (Exception $e) {
$error = getError('There was an unexpected error. Please try again.', $e->getMessage());
Messages::flash('ERROR', 'DEFAULT', 'There was an unexpected error. Please try again.');
}
// Show configured login message if any
if (!empty($config['login_message'])) {
$notice = $config['login_message'];
include '../app/templates/block-message.php';
echo Messages::render('NOTICE', 'DEFAULT', $config['login_message'], false);
}
// Get any new messages
include '../app/includes/messages.php';
include '../app/includes/messages-show.php';
// Load the template
include '../app/templates/form-login.php';
?>

View File

@ -8,12 +8,9 @@
* and redirects to the login page on success or displays an error message on failure.
*/
// check if the registration is allowed
// registration is allowed, go on
if ($config['registration_enabled'] === true) {
// clear any previous error messages
unset($error);
try {
// connect to database
@ -28,27 +25,30 @@ if ($config['registration_enabled'] === true) {
// redirect to login
if ($result === true) {
$_SESSION['notice'] = "Registration successful.<br />You can log in now.";
Messages::flash('NOTICE', 'DEFAULT', "Registration successful.<br />You can log in now.");
header('Location: ' . htmlspecialchars($app_root));
exit();
// registration fail, redirect to login
} else {
$_SESSION['error'] = "Registration failed. $result";
Messages::flash('ERROR', 'DEFAULT', "Registration failed. $result");
header('Location: ' . htmlspecialchars($app_root));
exit();
}
}
} catch (Exception $e) {
$error = $e->getMessage();
Messages::flash('ERROR', 'DEFAULT', $e->getMessage());
}
include '../app/templates/block-message.php';
// Get any new messages
include '../app/includes/messages.php';
include '../app/includes/messages-show.php';
// Load the template
include '../app/templates/form-register.php';
// registration disabled
} else {
$notice = 'Registration is disabled';
include '../app/templates/block-message.php';
echo Messages::render('NOTICE', 'DEFAULT', 'Registration is disabled', false);
}
?>

View File

@ -9,15 +9,18 @@ if (!($userObject->hasRight($user_id, 'superuser') ||
exit;
}
// Include Messages class
require_once '../app/classes/messages.php';
// Initialize variables for feedback messages
$messages = [];
if (!isset($currentUser)) {
include '../app/templates/error-unauthorized.php';
exit;
}
// Get current section
$section = isset($_POST['section']) ? $_POST['section'] : (isset($_GET['section']) ? $_GET['section'] : 'whitelist');
// Initialize RateLimiter
require_once '../app/classes/ratelimiter.php';
$rateLimiter = new RateLimiter($dbWeb);
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$action = $_POST['action'];
@ -81,6 +84,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
}
} catch (Exception $e) {
$messages[] = ['category' => 'SECURITY', 'key' => 'CUSTOM_ERROR', 'custom_message' => $e->getMessage()];
Messages::flash('SECURITY', 'CUSTOM_ERROR', 'custom_message');
}
if (empty($messages)) {
@ -90,16 +94,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
}
}
// Get flash messages from previous request
$flash_messages = Messages::getFlash();
$messages = array_merge($messages, array_map(function($flash) {
return [
'category' => $flash['category'],
'key' => $flash['key'],
'custom_message' => $flash['custom_message']
];
}, $flash_messages));
// Always show rate limit info message for rate limiting section
if ($section === 'ratelimit') {
$messages[] = ['category' => 'SECURITY', 'key' => 'RATE_LIMIT_INFO'];
@ -109,7 +103,11 @@ if ($section === 'ratelimit') {
$whitelisted = $rateLimiter->getWhitelistedIps();
$blacklisted = $rateLimiter->getBlacklistedIps();
// Include template
// Get any new messages
include '../app/includes/messages.php';
include '../app/includes/messages-show.php';
// Load the template
include '../app/templates/security.php';
?>

View File

@ -1,18 +0,0 @@
<?php
// Display and clean up session messages
foreach (['error', 'notice'] as $type) {
if (isset($_SESSION[$type])) {
renderMessage($_SESSION[$type], $type, true);
}
}
// Display standalone messages
if (isset($error)) {
renderMessage($error, 'error', true);
}
if (isset($notice)) {
renderMessage($notice, 'notice', true);
}
?>

View File

@ -18,6 +18,28 @@ $(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
});
</script>
<script>
// dismissible messages
document.addEventListener('DOMContentLoaded', function() {
// Initialize Bootstrap alerts
var alerts = document.querySelectorAll('.alert');
alerts.forEach(function(alert) {
var closeButton = alert.querySelector('.btn-close');
if (closeButton) {
closeButton.addEventListener('click', function() {
alert.classList.remove('show');
setTimeout(function() {
alert.remove();
}, 150);
});
}
});
});
</script>
</body>
</html>

View File

@ -37,3 +37,13 @@
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col">
<?php if (isset($messages) && is_array($messages)): ?>
<?php foreach ($messages as $msg): ?>
<?= Messages::render($msg['category'], $msg['key'], $msg['custom_message'] ?? null) ?>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>

View File

@ -3,9 +3,6 @@
<div class="row mb-4">
<div class="col">
<h2>Security Settings</h2>
<?php foreach ($messages as $msg): ?>
<?= Messages::render($msg['category'], $msg['key'], $msg['custom_message'] ?? null) ?>
<?php endforeach; ?>
<ul class="nav nav-tabs">
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) { ?>
<li class="nav-item">
@ -172,7 +169,7 @@
<div class="card">
<div class="card-header">
<h3>Rate Limiting Settings</h3>
Restricts brute force or flooding attempts at login page.
Rate limiting settings control how many failed login attempts are allowed before blocking an IP address.
</div>
<div class="card-body">
<div class="alert alert-info">
@ -224,21 +221,3 @@
<?php } ?>
</div>
<!-- /Security Settings -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Bootstrap alerts
var alerts = document.querySelectorAll('.alert');
alerts.forEach(function(alert) {
var closeButton = alert.querySelector('.btn-close');
if (closeButton) {
closeButton.addEventListener('click', function() {
alert.classList.remove('show');
setTimeout(function() {
alert.remove();
}, 150);
});
}
});
});
</script>

View File

@ -18,14 +18,14 @@ ob_start();
// sanitize all input vars that may end up in URLs or forms
require '../app/includes/sanitize.php';
require '../app/includes/errors.php';
// Include Messages class
// Initialize message system
require_once '../app/classes/messages.php';
// Initialize variables for feedback messages
$messages = [];
include '../app/includes/messages.php';
require '../app/includes/errors.php';
// error reporting, comment out in production
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
@ -104,12 +104,19 @@ if ( !isset($_COOKIE['username']) && ($page !== 'login' && $page !== 'register')
// connect to db of Jilo Web
require '../app/classes/database.php';
require '../app/includes/database.php';
try {
$response = connectDB($config);
if ($response['db'] === null) {
$error .= $response['error'];
// include '../app/templates/block-message.php';
} else {
if (!$response['db']) {
throw new Exception('Could not connect to database: ' . $response['error']);
}
$dbWeb = $response['db'];
} catch (Exception $e) {
Messages::flash('ERROR', 'DEFAULT', getError('Error connecting to the database.', $e->getMessage()));
include '../app/templates/page-header.php';
include '../app/includes/messages.php';
include '../app/includes/messages-show.php';
include '../app/templates/page-footer.php';
exit();
}
// start logging
@ -146,38 +153,17 @@ if ($page == 'logout') {
session_destroy();
setcookie('username', "", time() - 100, $config['folder'], $config['domain'], isset($_SERVER['HTTPS']), true);
$notice = "You were logged out.<br />You can log in again.";
// Log successful logout
$user_id = $userObject->getUserId($currentUser)[0]['id'];
$logObject->insertLog($user_id, "Logout: User \"$currentUser\" logged out. IP: $user_IP", 'user');
// Set success message
Messages::flash('LOGIN', 'LOGOUT_SUCCESS');
include '../app/templates/page-header.php';
include '../app/templates/page-menu.php';
include '../app/templates/block-message.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 {
// if user is logged in, we need user details and rights
@ -202,9 +188,7 @@ if ($page == 'logout') {
$server_endpoint = '/health';
$server_status = $serverObject->getServerStatus($server_host, $server_port, $server_endpoint);
if (!$server_status) {
$error = 'The Jilo Server is not running. Some data may be old and incorrect.';
Messages::get('SECURITY', 'RATE_LIMIT_INFO');
Messages::render('SECURITY', 'RATE_LIMIT_INFO');
echo Messages::render('ERROR', 'DEFAULT', 'The Jilo Server is not running. Some data may be old and incorrect.', false);
}
}
@ -214,7 +198,6 @@ if ($page == 'logout') {
if (isset($currentUser)) {
include '../app/templates/page-sidebar.php';
}
include '../app/templates/block-message.php';
if (in_array($page, $allowed_urls)) {
// all normal pages
include "../app/pages/{$page}.php";