Audit fixes: security, dead code removal, API consolidation, JS dedup
Security: - Fix IDOR in delete/update comment (add ticket visibility check) - XSS defense-in-depth in DashboardView active filters - Replace innerHTML with DOM construction in toast.js - Remove redundant real_escape_string in check_duplicates - Add rate limiting to get_template, download_attachment, audit_log, saved_filters, user_preferences endpoints Bug fixes: - Session timeout now reads from config instead of hardcoded 18000 - TicketController uses $GLOBALS['config'] instead of duplicate .env parsing - Add DISCORD_WEBHOOK_URL to centralized config - Cleanup script uses hashmap for O(1) ticket ID lookups Dead code removal (~100 lines): - Remove dead getTicketComments() from TicketModel (wrong bind_param type) - Remove dead getCategories()/getTypes() from DashboardController - Remove ~80 lines dead Discord webhook code from update_ticket API Consolidation: - Create api/bootstrap.php for shared API setup (auth, CSRF, rate limit) - Convert 6 API endpoints to use bootstrap - Extract escapeHtml/getTicketIdFromUrl into shared utils.js - Batch save for user preferences (1 request instead of 7) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,28 +15,7 @@ try {
|
||||
$configPath = dirname(__DIR__) . '/config/config.php';
|
||||
require_once $configPath;
|
||||
require_once dirname(__DIR__) . '/helpers/Database.php';
|
||||
require_once dirname(__DIR__) . '/helpers/UrlHelper.php';
|
||||
|
||||
// Load environment variables (for Discord webhook)
|
||||
$envPath = dirname(__DIR__) . '/.env';
|
||||
$envVars = [];
|
||||
if (file_exists($envPath)) {
|
||||
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, '=') !== false && strpos($line, '#') !== 0) {
|
||||
list($key, $value) = explode('=', $line, 2);
|
||||
$key = trim($key);
|
||||
$value = trim($value);
|
||||
// Remove surrounding quotes if present
|
||||
if ((substr($value, 0, 1) === '"' && substr($value, -1) === '"') ||
|
||||
(substr($value, 0, 1) === "'" && substr($value, -1) === "'")) {
|
||||
$value = substr($value, 1, -1);
|
||||
}
|
||||
$envVars[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load models directly with absolute paths
|
||||
$ticketModelPath = dirname(__DIR__) . '/models/TicketModel.php';
|
||||
$commentModelPath = dirname(__DIR__) . '/models/CommentModel.php';
|
||||
@@ -76,16 +55,14 @@ try {
|
||||
private $commentModel;
|
||||
private $auditLog;
|
||||
private $workflowModel;
|
||||
private $envVars;
|
||||
private $userId;
|
||||
private $isAdmin;
|
||||
|
||||
public function __construct($conn, $envVars = [], $userId = null, $isAdmin = false) {
|
||||
public function __construct($conn, $userId = null, $isAdmin = false) {
|
||||
$this->ticketModel = new TicketModel($conn);
|
||||
$this->commentModel = new CommentModel($conn);
|
||||
$this->auditLog = new AuditLogModel($conn);
|
||||
$this->workflowModel = new WorkflowModel($conn);
|
||||
$this->envVars = $envVars;
|
||||
$this->userId = $userId;
|
||||
$this->isAdmin = $isAdmin;
|
||||
}
|
||||
@@ -184,9 +161,6 @@ try {
|
||||
$this->auditLog->logTicketUpdate($this->userId, $id, $data);
|
||||
}
|
||||
|
||||
// Discord webhook disabled for updates - only send for new tickets
|
||||
// $this->sendDiscordWebhook($id, $currentTicket, $updateData, $data);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => $updateData['status'],
|
||||
@@ -194,87 +168,6 @@ try {
|
||||
'message' => 'Ticket updated successfully'
|
||||
];
|
||||
}
|
||||
|
||||
private function sendDiscordWebhook($ticketId, $oldData, $newData, $changedFields) {
|
||||
if (!isset($this->envVars['DISCORD_WEBHOOK_URL']) || empty($this->envVars['DISCORD_WEBHOOK_URL'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$webhookUrl = $this->envVars['DISCORD_WEBHOOK_URL'];
|
||||
|
||||
// Determine what fields actually changed
|
||||
$changes = [];
|
||||
foreach ($changedFields as $field => $newValue) {
|
||||
if ($field === 'ticket_id') continue; // Skip ticket_id
|
||||
|
||||
$oldValue = $oldData[$field] ?? 'N/A';
|
||||
if ($oldValue != $newValue) {
|
||||
$changes[] = [
|
||||
'name' => ucfirst($field),
|
||||
'value' => "$oldValue → $newValue",
|
||||
'inline' => true
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($changes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create ticket URL using validated host
|
||||
$ticketUrl = UrlHelper::ticketUrl($ticketId);
|
||||
|
||||
// Determine embed color based on priority
|
||||
$colors = [
|
||||
1 => 0xff4d4d, // Red
|
||||
2 => 0xffa726, // Orange
|
||||
3 => 0x42a5f5, // Blue
|
||||
4 => 0x66bb6a, // Green
|
||||
5 => 0x9e9e9e // Gray
|
||||
];
|
||||
$color = $colors[$newData['priority']] ?? 0x3498db;
|
||||
|
||||
$embed = [
|
||||
'title' => '🔄 Ticket Updated',
|
||||
'description' => "**#{$ticketId}** - " . $newData['title'],
|
||||
'color' => $color,
|
||||
'fields' => array_merge($changes, [
|
||||
[
|
||||
'name' => '🔗 View Ticket',
|
||||
'value' => "[Click here to view]($ticketUrl)",
|
||||
'inline' => false
|
||||
]
|
||||
]),
|
||||
'footer' => [
|
||||
'text' => 'Tinker Tickets'
|
||||
],
|
||||
'timestamp' => date('c')
|
||||
];
|
||||
|
||||
$payload = [
|
||||
'embeds' => [$embed]
|
||||
];
|
||||
|
||||
// Send webhook
|
||||
$ch = curl_init($webhookUrl);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
|
||||
$webhookResult = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// Log webhook errors instead of silencing them
|
||||
if ($curlError) {
|
||||
error_log("Discord webhook cURL error for ticket #{$ticketId}: {$curlError}");
|
||||
} elseif ($httpCode !== 204 && $httpCode !== 200) {
|
||||
error_log("Discord webhook failed for ticket #{$ticketId}. HTTP Code: {$httpCode}, Response: " . substr($webhookResult, 0, 200));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use centralized database connection
|
||||
@@ -300,7 +193,7 @@ try {
|
||||
$ticketId = (int)$data['ticket_id'];
|
||||
|
||||
// Initialize controller
|
||||
$controller = new ApiTicketController($conn, $envVars, $userId, $isAdmin);
|
||||
$controller = new ApiTicketController($conn, $userId, $isAdmin);
|
||||
|
||||
// Update ticket
|
||||
$result = $controller->update($ticketId, $data);
|
||||
|
||||
Reference in New Issue
Block a user