Apply web_template gap analysis improvements (P1-P3)
P1-A: Fix CSP - add fonts.googleapis.com to style-src, fonts.gstatic.com to font-src
P1-B: CSRF token rotation - add rotateToken() to CsrfMiddleware; bootstrap.php rotates
after successful validation and stores in $GLOBALS['_new_csrf_token']; add
apiRespond() helper to append token to responses; lt.api interceptor in
layout_footer.php auto-updates window.CSRF_TOKEN from responses
P1-C: Styled 403/404 error views with TDS layout instead of raw text; index.php now
uses requireAdmin() helper eliminating 7 duplicated guard blocks (P3-D)
P2-A: Remove duplicate JS-generated keyboard help modal from keyboard-shortcuts.js;
'?' key now routes to static #lt-keys-help modal in footer
P2-B: Asset versioning driven by config ASSET_VERSION key; base.css and base.js get
?v= cache-busting in layout_header.php
P2-C: Add data-theme="dark" to <html> tag to prevent FOUC on light-mode users
P2-E: Escape status value in dashboard.js hover preview class attribute via lt.escHtml()
P2-F: Replace bespoke showLoadingOverlay() with lt-spinner / lt-loading-text from
base.css; add .lt-loading-overlay wrapper CSS to dashboard.css
P2-G: Add keyboard-shortcuts.js to all 7 admin views so J/K nav and ? help work
P3-A: APP_NAME, APP_SUBTITLE, APP_VERSION driven from config.php; layout header/footer
use config values instead of hardcoded strings
P3-G: Replace custom initTableSorting() with lt.sortTable.init() which manages aria-sort
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,15 @@ if (!str_starts_with($requestPath, '/api/')) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: require admin or render styled 403 and exit
|
||||
function requireAdmin(?array $user): void {
|
||||
if (!$user || empty($user['is_admin'])) {
|
||||
http_response_code(403);
|
||||
include __DIR__ . '/views/error_403.php';
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Simple router
|
||||
switch (true) {
|
||||
case $requestPath == '/' || $requestPath == '':
|
||||
@@ -176,11 +185,7 @@ switch (true) {
|
||||
|
||||
// Admin Routes - require admin privileges
|
||||
case $requestPath == '/admin/recurring-tickets':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
requireAdmin($currentUser);
|
||||
require_once 'models/RecurringTicketModel.php';
|
||||
$recurringModel = new RecurringTicketModel($conn);
|
||||
$recurringTickets = $recurringModel->getAll(true);
|
||||
@@ -188,11 +193,7 @@ switch (true) {
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/custom-fields':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
requireAdmin($currentUser);
|
||||
require_once 'models/CustomFieldModel.php';
|
||||
$fieldModel = new CustomFieldModel($conn);
|
||||
$customFields = $fieldModel->getAllDefinitions(null, false);
|
||||
@@ -200,11 +201,7 @@ switch (true) {
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/workflow':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
requireAdmin($currentUser);
|
||||
$result = $conn->query("SELECT * FROM status_transitions ORDER BY from_status, to_status");
|
||||
$workflows = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
@@ -214,11 +211,7 @@ switch (true) {
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/templates':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
requireAdmin($currentUser);
|
||||
$result = $conn->query("SELECT * FROM ticket_templates ORDER BY template_name");
|
||||
$templates = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
@@ -228,11 +221,7 @@ switch (true) {
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/audit-log':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
requireAdmin($currentUser);
|
||||
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
|
||||
$perPage = 50;
|
||||
$offset = ($page - 1) * $perPage;
|
||||
@@ -314,11 +303,7 @@ switch (true) {
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/api-keys':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
requireAdmin($currentUser);
|
||||
require_once 'models/ApiKeyModel.php';
|
||||
$apiKeyModel = new ApiKeyModel($conn);
|
||||
$apiKeys = $apiKeyModel->getAllKeys();
|
||||
@@ -326,11 +311,7 @@ switch (true) {
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/user-activity':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
requireAdmin($currentUser);
|
||||
|
||||
$dateRange = [
|
||||
'from' => $_GET['date_from'] ?? date('Y-m-d', strtotime('-30 days')),
|
||||
@@ -407,9 +388,8 @@ switch (true) {
|
||||
exit;
|
||||
|
||||
default:
|
||||
// 404 Not Found
|
||||
header("HTTP/1.0 404 Not Found");
|
||||
echo '404 Page Not Found';
|
||||
http_response_code(404);
|
||||
include __DIR__ . '/views/error_404.php';
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user