Implements and troubleshoots new messages system
parent
4bfae911db
commit
b314cdd14d
|
@ -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
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
class RateLimiter {
|
||||
public $db;
|
||||
private $log;
|
||||
public $maxAttempts = 5; // Maximum login attempts
|
||||
public $decayMinutes = 15; // Time window in minutes
|
||||
public $maxAttempts = 5; // Maximum login attempts
|
||||
public $decayMinutes = 15; // Time window in minutes
|
||||
public $autoBlacklistThreshold = 10; // Attempts before auto-blacklist
|
||||
public $autoBlacklistDuration = 24; // Hours to blacklist for
|
||||
public $ratelimitTable = 'login_attempts';
|
||||
|
@ -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'))
|
||||
)";
|
||||
|
@ -45,7 +45,7 @@ class RateLimiter {
|
|||
ip_address TEXT NOT NULL UNIQUE,
|
||||
is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)),
|
||||
reason TEXT,
|
||||
expiry_time TEXT NULL,
|
||||
expiry_time TEXT NULL,
|
||||
created_at TEXT DEFAULT (DATETIME('now')),
|
||||
created_by TEXT
|
||||
)";
|
||||
|
@ -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,27 +87,21 @@ 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}");
|
||||
$stmt->execute();
|
||||
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
if ($row['is_network']) {
|
||||
if ($this->ipInRange($ip, $row['ip_address'])) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if ($ip === $row['ip_address']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
/**
|
||||
* 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']);
|
||||
}
|
||||
|
||||
private function isIpBlacklisted($ip) {
|
||||
/**
|
||||
* 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();
|
||||
|
@ -132,6 +126,29 @@ class RateLimiter {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)) {
|
||||
if ($row['is_network']) {
|
||||
if ($this->ipInRange($ip, $row['ip_address'])) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if ($ip === $row['ip_address']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function ipInRange($ip, $cidr) {
|
||||
list($subnet, $bits) = explode('/', $cidr);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -390,9 +414,9 @@ class RateLimiter {
|
|||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([
|
||||
':ip' => $ipAddress,
|
||||
':username' => $username,
|
||||
':minutes' => $this->decayMinutes
|
||||
':ip' => $ipAddress,
|
||||
':username' => $username,
|
||||
':minutes' => $this->decayMinutes
|
||||
]);
|
||||
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
@ -405,7 +429,7 @@ class RateLimiter {
|
|||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([
|
||||
':minutes' => $this->decayMinutes
|
||||
':minutes' => $this->decayMinutes
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -418,9 +442,9 @@ class RateLimiter {
|
|||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([
|
||||
':ip' => $ipAddress,
|
||||
':username' => $username,
|
||||
':minutes' => $this->decayMinutes
|
||||
':ip' => $ipAddress,
|
||||
':username' => $username,
|
||||
':minutes' => $this->decayMinutes
|
||||
]);
|
||||
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -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"',
|
||||
],
|
||||
];
|
|
@ -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));
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
?>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -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';
|
||||
|
||||
?>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
?>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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';
|
||||
$response = connectDB($config);
|
||||
if ($response['db'] === null) {
|
||||
$error .= $response['error'];
|
||||
// include '../app/templates/block-message.php';
|
||||
} else {
|
||||
try {
|
||||
$response = connectDB($config);
|
||||
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";
|
||||
|
|
Loading…
Reference in New Issue