Add security logging, domain validation, and output helpers

- Add authentication failure logging to AuthMiddleware (session expiry,
  access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
  configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
  json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
  date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 18:51:16 -05:00
parent 44f2c21f2d
commit 5b2a2c271e
8 changed files with 528 additions and 42 deletions

View File

@@ -13,6 +13,39 @@ class AuthMiddleware {
$this->userModel = new UserModel($conn);
}
/**
* Log security event for authentication failures
*
* @param string $event Event type (e.g., 'auth_required', 'access_denied', 'session_expired')
* @param array $context Additional context data
*/
private function logSecurityEvent(string $event, array $context = []): void {
$logData = [
'event' => $event,
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'forwarded_for' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null,
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
'request_uri' => $_SERVER['REQUEST_URI'] ?? 'unknown',
'request_method' => $_SERVER['REQUEST_METHOD'] ?? 'unknown',
'timestamp' => date('c')
];
// Merge additional context
$logData = array_merge($logData, $context);
// Remove null values for cleaner logs
$logData = array_filter($logData, fn($v) => $v !== null);
// Format log message
$message = sprintf(
"[SECURITY] %s: %s",
strtoupper($event),
json_encode($logData, JSON_UNESCAPED_SLASHES)
);
error_log($message);
}
/**
* Authenticate user from Authelia forward auth headers
*
@@ -37,6 +70,13 @@ class AuthMiddleware {
if (isset($_SESSION['user']) && isset($_SESSION['user']['user_id'])) {
// Verify session hasn't expired (5 hour timeout)
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > 18000)) {
// Log session expiration
$this->logSecurityEvent('session_expired', [
'username' => $_SESSION['user']['username'] ?? 'unknown',
'user_id' => $_SESSION['user']['user_id'] ?? null,
'session_age_seconds' => time() - $_SESSION['last_activity']
]);
// Session expired, clear it
session_unset();
session_destroy();
@@ -123,6 +163,11 @@ class AuthMiddleware {
* Redirect to Authelia login
*/
private function redirectToAuth() {
// Log unauthenticated access attempt
$this->logSecurityEvent('auth_required', [
'reason' => 'no_auth_headers'
]);
// Redirect to the auth endpoint (Authelia will handle the redirect back)
header('HTTP/1.1 401 Unauthorized');
echo '<!DOCTYPE html>
@@ -187,6 +232,14 @@ class AuthMiddleware {
* @param string $groups User groups
*/
private function showAccessDenied($username, $groups) {
// Log access denied event with user details
$this->logSecurityEvent('access_denied', [
'username' => $username,
'groups' => $groups ?: 'none',
'required_groups' => 'admin,employee',
'reason' => 'insufficient_group_membership'
]);
header('HTTP/1.1 403 Forbidden');
echo '<!DOCTYPE html>
<html>