feat: Add CSRF tokens to all JavaScript fetch calls and fix XSS
Security improvements across all JavaScript files: CSRF Protection: - assets/js/ticket.js - Added X-CSRF-Token header to 5 fetch calls (update_ticket.php x3, add_comment.php, assign_ticket.php) - assets/js/dashboard.js - Added X-CSRF-Token to 8 fetch calls (update_ticket.php x2, bulk_operation.php x6) - assets/js/settings.js - Added X-CSRF-Token to user preferences save - assets/js/advanced-search.js - Added X-CSRF-Token to filter save/delete XSS Prevention: - assets/js/ticket.js:183-209 - Replaced insertAdjacentHTML() with safe DOM API (createElement/textContent) to prevent script injection in comment rendering. User-supplied data (user_name, created_at) now auto-escaped via textContent. All state-changing operations now include CSRF token validation. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -156,7 +156,10 @@ async function saveCurrentFilter() {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('/api/saved_filters.php', {
|
const response = await fetch('/api/saved_filters.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
filter_name: filterName.trim(),
|
filter_name: filterName.trim(),
|
||||||
filter_criteria: filterCriteria
|
filter_criteria: filterCriteria
|
||||||
@@ -319,7 +322,10 @@ async function deleteSavedFilter() {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('/api/saved_filters.php', {
|
const response = await fetch('/api/saved_filters.php', {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
|
},
|
||||||
body: JSON.stringify({ filter_id: filterId })
|
body: JSON.stringify({ filter_id: filterId })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -276,7 +276,8 @@ function quickSave() {
|
|||||||
fetch('/api/update_ticket.php', {
|
fetch('/api/update_ticket.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
})
|
})
|
||||||
@@ -355,7 +356,8 @@ function saveTicket() {
|
|||||||
fetch('/api/update_ticket.php', {
|
fetch('/api/update_ticket.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
ticket_id: ticketId,
|
ticket_id: ticketId,
|
||||||
@@ -492,7 +494,10 @@ function bulkClose() {
|
|||||||
|
|
||||||
fetch('/api/bulk_operation.php', {
|
fetch('/api/bulk_operation.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
operation_type: 'bulk_close',
|
operation_type: 'bulk_close',
|
||||||
ticket_ids: ticketIds
|
ticket_ids: ticketIds
|
||||||
@@ -593,7 +598,10 @@ function performBulkAssign() {
|
|||||||
|
|
||||||
fetch('/api/bulk_operation.php', {
|
fetch('/api/bulk_operation.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
operation_type: 'bulk_assign',
|
operation_type: 'bulk_assign',
|
||||||
ticket_ids: ticketIds,
|
ticket_ids: ticketIds,
|
||||||
@@ -681,7 +689,10 @@ function performBulkPriority() {
|
|||||||
|
|
||||||
fetch('/api/bulk_operation.php', {
|
fetch('/api/bulk_operation.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
operation_type: 'bulk_priority',
|
operation_type: 'bulk_priority',
|
||||||
ticket_ids: ticketIds,
|
ticket_ids: ticketIds,
|
||||||
@@ -800,15 +811,18 @@ function closeBulkStatusModal() {
|
|||||||
function performBulkStatusChange() {
|
function performBulkStatusChange() {
|
||||||
const status = document.getElementById('bulkStatus').value;
|
const status = document.getElementById('bulkStatus').value;
|
||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
alert('Please select a status');
|
alert('Please select a status');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('/api/bulk_operation.php', {
|
fetch('/api/bulk_operation.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
operation_type: 'bulk_status',
|
operation_type: 'bulk_status',
|
||||||
ticket_ids: ticketIds,
|
ticket_ids: ticketIds,
|
||||||
@@ -885,10 +899,13 @@ function closeBulkDeleteModal() {
|
|||||||
|
|
||||||
function performBulkDelete() {
|
function performBulkDelete() {
|
||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
fetch('/api/bulk_operation.php', {
|
fetch('/api/bulk_operation.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
operation_type: 'bulk_delete',
|
operation_type: 'bulk_delete',
|
||||||
ticket_ids: ticketIds
|
ticket_ids: ticketIds
|
||||||
|
|||||||
@@ -81,7 +81,10 @@ async function saveSettings() {
|
|||||||
for (const [key, value] of Object.entries(prefs)) {
|
for (const [key, value] of Object.entries(prefs)) {
|
||||||
const response = await fetch('/api/user_preferences.php', {
|
const response = await fetch('/api/user_preferences.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
|
},
|
||||||
body: JSON.stringify({ key, value })
|
body: JSON.stringify({ key, value })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ function saveTicket() {
|
|||||||
fetch(apiUrl, {
|
fetch(apiUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
ticket_id: ticketId,
|
ticket_id: ticketId,
|
||||||
@@ -140,7 +141,8 @@ function addComment() {
|
|||||||
fetch('/api/add_comment.php', {
|
fetch('/api/add_comment.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
ticket_id: ticketId,
|
ticket_id: ticketId,
|
||||||
@@ -178,18 +180,33 @@ function addComment() {
|
|||||||
.replace(/\n/g, '<br>');
|
.replace(/\n/g, '<br>');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new comment to the list
|
// Add new comment to the list (using safe DOM API to prevent XSS)
|
||||||
const commentsList = document.querySelector('.comments-list');
|
const commentsList = document.querySelector('.comments-list');
|
||||||
const newComment = `
|
|
||||||
<div class="comment">
|
const commentDiv = document.createElement('div');
|
||||||
<div class="comment-header">
|
commentDiv.className = 'comment';
|
||||||
<span class="comment-user">${data.user_name}</span>
|
|
||||||
<span class="comment-date">${data.created_at}</span>
|
const headerDiv = document.createElement('div');
|
||||||
</div>
|
headerDiv.className = 'comment-header';
|
||||||
<div class="comment-text">${displayText}</div>
|
|
||||||
</div>
|
const userSpan = document.createElement('span');
|
||||||
`;
|
userSpan.className = 'comment-user';
|
||||||
commentsList.insertAdjacentHTML('afterbegin', newComment);
|
userSpan.textContent = data.user_name; // Safe - auto-escapes
|
||||||
|
|
||||||
|
const dateSpan = document.createElement('span');
|
||||||
|
dateSpan.className = 'comment-date';
|
||||||
|
dateSpan.textContent = data.created_at; // Safe - auto-escapes
|
||||||
|
|
||||||
|
const textDiv = document.createElement('div');
|
||||||
|
textDiv.className = 'comment-text';
|
||||||
|
textDiv.innerHTML = displayText; // displayText already sanitized above
|
||||||
|
|
||||||
|
headerDiv.appendChild(userSpan);
|
||||||
|
headerDiv.appendChild(dateSpan);
|
||||||
|
commentDiv.appendChild(headerDiv);
|
||||||
|
commentDiv.appendChild(textDiv);
|
||||||
|
|
||||||
|
commentsList.insertBefore(commentDiv, commentsList.firstChild);
|
||||||
} else {
|
} else {
|
||||||
console.error('Error adding comment:', data.error || 'Unknown error');
|
console.error('Error adding comment:', data.error || 'Unknown error');
|
||||||
}
|
}
|
||||||
@@ -283,7 +300,10 @@ function handleAssignmentChange() {
|
|||||||
|
|
||||||
fetch('/api/assign_ticket.php', {
|
fetch('/api/assign_ticket.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
|
},
|
||||||
body: JSON.stringify({ ticket_id: ticketId, assigned_to: assignedTo })
|
body: JSON.stringify({ ticket_id: ticketId, assigned_to: assignedTo })
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@@ -316,7 +336,10 @@ function handleMetadataChanges() {
|
|||||||
|
|
||||||
fetch('/api/update_ticket.php', {
|
fetch('/api/update_ticket.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
ticket_id: ticketId,
|
ticket_id: ticketId,
|
||||||
[fieldName]: fieldName === 'priority' ? parseInt(newValue) : newValue
|
[fieldName]: fieldName === 'priority' ? parseInt(newValue) : newValue
|
||||||
@@ -418,7 +441,8 @@ function updateTicketStatus() {
|
|||||||
fetch('/api/update_ticket.php', {
|
fetch('/api/update_ticket.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.CSRF_TOKEN
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
ticket_id: ticketId,
|
ticket_id: ticketId,
|
||||||
|
|||||||
Reference in New Issue
Block a user