Adds new message system with dismissible messages

main
Yasen Pramatarov 2025-01-04 14:22:53 +02:00
parent eae2a8a47c
commit 608946ddee
3 changed files with 180 additions and 42 deletions

View File

@ -0,0 +1,137 @@
<?php
class Messages {
// Message types
const TYPE_SUCCESS = 'success';
const TYPE_ERROR = 'danger';
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.',
'type' => self::TYPE_INFO,
'dismissible' => false
]
];
const LOGIN = [
'LOGIN_FAILED' => [
'message' => 'Invalid username or password.',
'type' => self::TYPE_ERROR,
'dismissible' => true
],
'LOGIN_BLOCKED' => [
'message' => 'Too many failed attempts. Please try again later.',
'type' => self::TYPE_ERROR,
'dismissible' => false
],
'IP_BLACKLISTED' => [
'message' => 'Access denied. Your IP address is blacklisted.',
'type' => self::TYPE_ERROR,
'dismissible' => false
]
];
/**
* Get message configuration by key
*/
public static function get($category, $key) {
$messages = constant("self::$category");
return $messages[$key] ?? null;
}
/**
* Render message HTML
*/
public static function render($category, $key, $customMessage = 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>' : '';
return sprintf(
'<div class="alert alert-%s%s" role="alert">%s%s</div>',
$config['type'],
$dismissible,
htmlspecialchars($message),
$dismissButton
);
}
/**
* Store message in session for display after redirect
*/
public static function flash($category, $key, $customMessage = null) {
if (!isset($_SESSION['flash_messages'])) {
$_SESSION['flash_messages'] = [];
}
$_SESSION['flash_messages'][] = [
'category' => $category,
'key' => $key,
'custom_message' => $customMessage
];
}
/**
* Get and clear all flash messages
*/
public static function getFlash() {
$messages = $_SESSION['flash_messages'] ?? [];
unset($_SESSION['flash_messages']);
return $messages;
}
}

View File

@ -9,96 +9,106 @@ if (!($userObject->hasRight($user_id, 'superuser') ||
exit; exit;
} }
// Include Messages class
require_once '../app/classes/messages.php';
// Initialize variables for feedback messages // Initialize variables for feedback messages
$error_message = ''; $messages = [];
$success_message = '';
// Get current section
$section = isset($_POST['section']) ? $_POST['section'] : (isset($_GET['section']) ? $_GET['section'] : 'whitelist');
// Handle form submissions // Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$action = $_POST['action']; $action = $_POST['action'];
$section = isset($_POST['section']) ? $_POST['section'] : (isset($_GET['section']) ? $_GET['section'] : 'whitelist');
try { try {
switch ($action) { switch ($action) {
case 'add_whitelist': case 'add_whitelist':
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit whitelist')) { if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit whitelist')) {
throw new Exception('You do not have permission to modify the whitelist.'); throw new Exception(Messages::get('SECURITY', 'PERMISSION_DENIED')['message']);
} }
if (empty($_POST['ip_address'])) { if (empty($_POST['ip_address'])) {
throw new Exception('IP address is required.'); throw new Exception(Messages::get('SECURITY', 'IP_REQUIRED')['message']);
} }
$is_network = isset($_POST['is_network']) ? 1 : 0; $is_network = isset($_POST['is_network']) ? 1 : 0;
if (!$rateLimiter->addToWhitelist($_POST['ip_address'], $is_network, $_POST['description'] ?? '', $currentUser, $user_id)) { if (!$rateLimiter->addToWhitelist($_POST['ip_address'], $is_network, $_POST['description'] ?? '', $currentUser, $user_id)) {
throw new Exception('Failed to add IP to whitelist. Please check the IP format.'); throw new Exception(Messages::get('SECURITY', 'WHITELIST_ADD_ERROR')['message']);
} }
$success_message = 'IP address successfully added to whitelist.'; Messages::flash('SECURITY', 'WHITELIST_ADD_SUCCESS');
break; break;
case 'remove_whitelist': case 'remove_whitelist':
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit whitelist')) { if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit whitelist')) {
throw new Exception('You do not have permission to modify the whitelist.'); throw new Exception(Messages::get('SECURITY', 'PERMISSION_DENIED')['message']);
} }
if (empty($_POST['ip_address'])) { if (empty($_POST['ip_address'])) {
throw new Exception('IP address is required.'); throw new Exception(Messages::get('SECURITY', 'IP_REQUIRED')['message']);
} }
if (!$rateLimiter->removeFromWhitelist($_POST['ip_address'], $currentUser, $user_id)) { if (!$rateLimiter->removeFromWhitelist($_POST['ip_address'], $currentUser, $user_id)) {
throw new Exception('Failed to remove IP from whitelist.'); throw new Exception(Messages::get('SECURITY', 'WHITELIST_REMOVE_ERROR')['message']);
} }
$success_message = 'IP address successfully removed from whitelist.'; Messages::flash('SECURITY', 'WHITELIST_REMOVE_SUCCESS');
break; break;
case 'add_blacklist': case 'add_blacklist':
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit blacklist')) { if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit blacklist')) {
throw new Exception('You do not have permission to modify the blacklist.'); throw new Exception(Messages::get('SECURITY', 'PERMISSION_DENIED')['message']);
} }
if (empty($_POST['ip_address'])) { if (empty($_POST['ip_address'])) {
throw new Exception('IP address is required.'); throw new Exception(Messages::get('SECURITY', 'IP_REQUIRED')['message']);
} }
$is_network = isset($_POST['is_network']) ? 1 : 0; $is_network = isset($_POST['is_network']) ? 1 : 0;
$expiry_hours = !empty($_POST['expiry_hours']) ? intval($_POST['expiry_hours']) : null; $expiry_hours = !empty($_POST['expiry_hours']) ? intval($_POST['expiry_hours']) : null;
if (!$rateLimiter->addToBlacklist($_POST['ip_address'], $is_network, $_POST['reason'] ?? '', $currentUser, $user_id, $expiry_hours)) { if (!$rateLimiter->addToBlacklist($_POST['ip_address'], $is_network, $_POST['reason'] ?? '', $currentUser, $user_id, $expiry_hours)) {
throw new Exception('Failed to add IP to blacklist. Please check the IP format.'); throw new Exception(Messages::get('SECURITY', 'BLACKLIST_ADD_ERROR')['message']);
} }
$success_message = 'IP address successfully added to blacklist.'; Messages::flash('SECURITY', 'BLACKLIST_ADD_SUCCESS');
break; break;
case 'remove_blacklist': case 'remove_blacklist':
if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit blacklist')) { if (!$userObject->hasRight($user_id, 'superuser') && !$userObject->hasRight($user_id, 'edit blacklist')) {
throw new Exception('You do not have permission to modify the blacklist.'); throw new Exception(Messages::get('SECURITY', 'PERMISSION_DENIED')['message']);
} }
if (empty($_POST['ip_address'])) { if (empty($_POST['ip_address'])) {
throw new Exception('IP address is required.'); throw new Exception(Messages::get('SECURITY', 'IP_REQUIRED')['message']);
} }
if (!$rateLimiter->removeFromBlacklist($_POST['ip_address'], $currentUser, $user_id)) { if (!$rateLimiter->removeFromBlacklist($_POST['ip_address'], $currentUser, $user_id)) {
throw new Exception('Failed to remove IP from blacklist.'); throw new Exception(Messages::get('SECURITY', 'BLACKLIST_REMOVE_ERROR')['message']);
} }
$success_message = 'IP address successfully removed from blacklist.'; Messages::flash('SECURITY', 'BLACKLIST_REMOVE_SUCCESS');
break; break;
} }
} catch (Exception $e) { } catch (Exception $e) {
$error_message = $e->getMessage(); $messages[] = ['category' => 'SECURITY', 'key' => 'CUSTOM_ERROR', 'custom_message' => $e->getMessage()];
} }
if (empty($error_message)) { if (empty($messages)) {
// Only redirect if there was no error // Only redirect if there were no errors
header("Location: {$app_root}?page=security&section={$section}" . header("Location: {$app_root}?page=security&section={$section}");
($success_message ? '&success=' . urlencode($success_message) : ''));
exit; exit;
} }
} }
// Get success message from URL if redirected after successful action // Get flash messages from previous request
if (isset($_GET['success'])) { $flash_messages = Messages::getFlash();
$success_message = $_GET['success']; $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'];
} }
// Get current lists // Get current lists
$whitelisted = $rateLimiter->getWhitelistedIps(); $whitelisted = $rateLimiter->getWhitelistedIps();
$blacklisted = $rateLimiter->getBlacklistedIps(); $blacklisted = $rateLimiter->getBlacklistedIps();
// Get current section
$section = isset($_GET['section']) ? $_GET['section'] : 'whitelist';
// Include template // Include template
include '../app/templates/security.php'; include '../app/templates/security.php';

View File

@ -3,18 +3,9 @@
<div class="row mb-4"> <div class="row mb-4">
<div class="col"> <div class="col">
<h2>Security Settings</h2> <h2>Security Settings</h2>
<?php if (!empty($error_message)): ?> <?php foreach ($messages as $msg): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert"> <?= Messages::render($msg['category'], $msg['key'], $msg['custom_message'] ?? null) ?>
<?= htmlspecialchars($error_message) ?> <?php endforeach; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if (!empty($success_message)): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?= htmlspecialchars($success_message) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) { ?> <?php if ($userObject->hasRight($user_id, 'superuser') || $userObject->hasRight($user_id, 'edit whitelist')) { ?>
<li class="nav-item"> <li class="nav-item">