Harden CSP by removing unsafe-inline for scripts

Refactored all inline event handlers (onclick, onchange, onsubmit) to use
addEventListener with data-action attributes and event delegation pattern.

Changes:
- views/*.php: Replaced inline handlers with data-action attributes
- views/admin/*.php: Same refactoring for all admin views
- assets/js/dashboard.js: Added event delegation for bulk/quick action modals
- assets/js/ticket.js: Added event delegation for dynamic elements
- assets/js/markdown.js: Refactored toolbar button handlers
- assets/js/keyboard-shortcuts.js: Refactored modal close button
- SecurityHeadersMiddleware.php: Enabled strict CSP with nonces

The CSP now uses script-src 'self' 'nonce-{nonce}' instead of 'unsafe-inline',
significantly improving XSS protection.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 13:15:55 -05:00
parent 37be81b3e2
commit c3f7593f3c
13 changed files with 564 additions and 158 deletions

View File

@@ -97,7 +97,7 @@ function initMobileSidebar() {
<span class="nav-icon">🏠</span>
<span>Home</span>
</a>
<button type="button" onclick="openMobileSidebar()">
<button type="button" data-action="open-mobile-sidebar">
<span class="nav-icon">🔍</span>
<span>Filter</span>
</button>
@@ -105,7 +105,7 @@ function initMobileSidebar() {
<span class="nav-icon"></span>
<span>New</span>
</a>
<button type="button" onclick="if(typeof openSettingsModal==='function')openSettingsModal()">
<button type="button" data-action="open-settings-modal">
<span class="nav-icon">⚙️</span>
<span>Settings</span>
</button>
@@ -136,20 +136,75 @@ document.addEventListener('DOMContentLoaded', function() {
window.location.href.includes('ticket.php') ||
document.querySelector('.ticket-details') !== null;
const isDashboard = hasTable && !isTicketPage;
if (isDashboard) {
// Dashboard-specific initialization
initStatusFilter();
initTableSorting();
initSidebarFilters();
}
// Initialize for all pages
initSettingsModal();
// Force dark mode only (terminal aesthetic - no theme switching)
document.documentElement.setAttribute('data-theme', 'dark');
document.body.classList.add('dark-mode');
// Event delegation for dynamically created modals
document.addEventListener('click', function(e) {
const target = e.target.closest('[data-action]');
if (!target) return;
const action = target.dataset.action;
switch (action) {
// Bulk operations
case 'perform-bulk-assign':
performBulkAssign();
break;
case 'close-bulk-assign-modal':
closeBulkAssignModal();
break;
case 'perform-bulk-priority':
performBulkPriority();
break;
case 'close-bulk-priority-modal':
closeBulkPriorityModal();
break;
case 'perform-bulk-status':
performBulkStatusChange();
break;
case 'close-bulk-status-modal':
closeBulkStatusModal();
break;
case 'perform-bulk-delete':
performBulkDelete();
break;
case 'close-bulk-delete-modal':
closeBulkDeleteModal();
break;
// Quick actions
case 'perform-quick-status':
performQuickStatusChange(target.dataset.ticketId);
break;
case 'close-quick-status-modal':
closeQuickStatusModal();
break;
case 'perform-quick-assign':
performQuickAssign(target.dataset.ticketId);
break;
case 'close-quick-assign-modal':
closeQuickAssignModal();
break;
// Mobile navigation
case 'open-mobile-sidebar':
if (typeof openMobileSidebar === 'function') openMobileSidebar();
break;
case 'open-settings-modal':
if (typeof openSettingsModal === 'function') openSettingsModal();
break;
}
});
});
function initTableSorting() {
@@ -716,8 +771,8 @@ function showBulkAssignModal() {
<div class="ascii-content">
<div class="modal-footer">
<button onclick="performBulkAssign()" class="btn btn-bulk">Assign</button>
<button onclick="closeBulkAssignModal()" class="btn btn-secondary">Cancel</button>
<button data-action="perform-bulk-assign" class="btn btn-bulk">Assign</button>
<button data-action="close-bulk-assign-modal" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
@@ -829,8 +884,8 @@ function showBulkPriorityModal() {
<div class="ascii-content">
<div class="modal-footer">
<button onclick="performBulkPriority()" class="btn btn-bulk">Update</button>
<button onclick="closeBulkPriorityModal()" class="btn btn-secondary">Cancel</button>
<button data-action="perform-bulk-priority" class="btn btn-bulk">Update</button>
<button data-action="close-bulk-priority-modal" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
@@ -963,14 +1018,14 @@ function showBulkStatusModal() {
<div class="ascii-content">
<div class="modal-footer">
<button onclick="performBulkStatusChange()" class="btn btn-bulk">Update</button>
<button onclick="closeBulkStatusModal()" class="btn btn-secondary">Cancel</button>
<button data-action="perform-bulk-status" class="btn btn-bulk">Update</button>
<button data-action="close-bulk-status-modal" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
}
@@ -1057,14 +1112,14 @@ function showBulkDeleteModal() {
<div class="ascii-content">
<div class="modal-footer">
<button onclick="performBulkDelete()" class="btn btn-bulk" style="background: var(--status-closed); border-color: var(--status-closed);">Delete Permanently</button>
<button onclick="closeBulkDeleteModal()" class="btn btn-secondary">Cancel</button>
<button data-action="perform-bulk-delete" class="btn btn-bulk" style="background: var(--status-closed); border-color: var(--status-closed);">Delete Permanently</button>
<button data-action="close-bulk-delete-modal" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
}
@@ -1332,8 +1387,8 @@ function quickStatusChange(ticketId, currentStatus) {
<div class="ascii-content">
<div class="modal-footer">
<button onclick="performQuickStatusChange('${ticketId}')" class="btn btn-primary">Update</button>
<button onclick="closeQuickStatusModal()" class="btn btn-secondary">Cancel</button>
<button data-action="perform-quick-status" data-ticket-id="${ticketId}" class="btn btn-primary">Update</button>
<button data-action="close-quick-status-modal" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
@@ -1407,8 +1462,8 @@ function quickAssign(ticketId) {
<div class="ascii-content">
<div class="modal-footer">
<button onclick="performQuickAssign('${ticketId}')" class="btn btn-primary">Assign</button>
<button onclick="closeQuickAssignModal()" class="btn btn-secondary">Cancel</button>
<button data-action="perform-quick-assign" data-ticket-id="${ticketId}" class="btn btn-primary">Assign</button>
<button data-action="close-quick-assign-modal" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>