Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Rate Limiting Middleware
|
|
|
|
|
*
|
2026-01-28 20:27:15 -05:00
|
|
|
* Implements both session-based and IP-based rate limiting to prevent abuse.
|
|
|
|
|
* IP-based limiting prevents attackers from bypassing limits by creating new sessions.
|
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
|
|
|
*/
|
|
|
|
|
class RateLimitMiddleware {
|
|
|
|
|
// Default limits
|
2026-01-28 20:27:15 -05:00
|
|
|
const DEFAULT_LIMIT = 100; // requests per window (session)
|
|
|
|
|
const API_LIMIT = 60; // API requests per window (session)
|
|
|
|
|
const IP_LIMIT = 300; // IP-based requests per window (more generous)
|
|
|
|
|
const IP_API_LIMIT = 120; // IP-based API requests per window
|
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
|
|
|
const WINDOW_SECONDS = 60; // 1 minute window
|
|
|
|
|
|
2026-01-28 20:27:15 -05:00
|
|
|
// Directory for IP rate limit storage
|
|
|
|
|
private static $rateLimitDir = null;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the rate limit storage directory
|
|
|
|
|
*
|
|
|
|
|
* @return string Path to rate limit storage directory
|
|
|
|
|
*/
|
|
|
|
|
private static function getRateLimitDir() {
|
|
|
|
|
if (self::$rateLimitDir === null) {
|
|
|
|
|
self::$rateLimitDir = sys_get_temp_dir() . '/tinker_tickets_ratelimit';
|
|
|
|
|
if (!is_dir(self::$rateLimitDir)) {
|
|
|
|
|
mkdir(self::$rateLimitDir, 0755, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return self::$rateLimitDir;
|
|
|
|
|
}
|
|
|
|
|
|
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
|
|
|
/**
|
2026-01-28 20:27:15 -05:00
|
|
|
* Get the client's IP address
|
|
|
|
|
*
|
|
|
|
|
* @return string Client IP address
|
|
|
|
|
*/
|
|
|
|
|
private static function getClientIp() {
|
|
|
|
|
// Check for forwarded IP (behind proxy/load balancer)
|
|
|
|
|
$headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP'];
|
|
|
|
|
foreach ($headers as $header) {
|
|
|
|
|
if (!empty($_SERVER[$header])) {
|
|
|
|
|
// Take the first IP in a comma-separated list
|
|
|
|
|
$ips = explode(',', $_SERVER[$header]);
|
|
|
|
|
$ip = trim($ips[0]);
|
|
|
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
|
|
|
|
return $ip;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check IP-based rate limit
|
|
|
|
|
*
|
|
|
|
|
* @param string $type 'default' or 'api'
|
|
|
|
|
* @return bool True if request is allowed, false if rate limited
|
|
|
|
|
*/
|
|
|
|
|
private static function checkIpRateLimit($type = 'default') {
|
|
|
|
|
$ip = self::getClientIp();
|
|
|
|
|
$limit = $type === 'api' ? self::IP_API_LIMIT : self::IP_LIMIT;
|
|
|
|
|
$now = time();
|
|
|
|
|
|
|
|
|
|
// Create a hash of the IP for the filename (security + filesystem safety)
|
|
|
|
|
$ipHash = md5($ip . '_' . $type);
|
|
|
|
|
$filePath = self::getRateLimitDir() . '/' . $ipHash . '.json';
|
|
|
|
|
|
|
|
|
|
// Load existing rate data
|
|
|
|
|
$rateData = ['count' => 0, 'window_start' => $now];
|
|
|
|
|
if (file_exists($filePath)) {
|
|
|
|
|
$content = @file_get_contents($filePath);
|
|
|
|
|
if ($content !== false) {
|
|
|
|
|
$decoded = json_decode($content, true);
|
|
|
|
|
if (is_array($decoded)) {
|
|
|
|
|
$rateData = $decoded;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if window has expired
|
|
|
|
|
if ($now - $rateData['window_start'] >= self::WINDOW_SECONDS) {
|
|
|
|
|
$rateData = ['count' => 0, 'window_start' => $now];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Increment count
|
|
|
|
|
$rateData['count']++;
|
|
|
|
|
|
|
|
|
|
// Save updated data
|
|
|
|
|
@file_put_contents($filePath, json_encode($rateData), LOCK_EX);
|
|
|
|
|
|
|
|
|
|
// Check if over limit
|
|
|
|
|
return $rateData['count'] <= $limit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clean up old rate limit files (call periodically)
|
|
|
|
|
*/
|
|
|
|
|
public static function cleanupOldFiles() {
|
|
|
|
|
$dir = self::getRateLimitDir();
|
|
|
|
|
$files = glob($dir . '/*.json');
|
|
|
|
|
$now = time();
|
|
|
|
|
$maxAge = self::WINDOW_SECONDS * 2; // Files older than 2 windows
|
|
|
|
|
|
|
|
|
|
foreach ($files as $file) {
|
|
|
|
|
if ($now - filemtime($file) > $maxAge) {
|
|
|
|
|
@unlink($file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check rate limit for current request (both session and IP)
|
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
|
|
|
*
|
|
|
|
|
* @param string $type 'default' or 'api'
|
|
|
|
|
* @return bool True if request is allowed, false if rate limited
|
|
|
|
|
*/
|
|
|
|
|
public static function check($type = 'default') {
|
2026-01-28 20:27:15 -05:00
|
|
|
// First check IP-based rate limit (prevents session bypass)
|
|
|
|
|
if (!self::checkIpRateLimit($type)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then check session-based rate limit
|
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
|
|
|
session_start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$limit = $type === 'api' ? self::API_LIMIT : self::DEFAULT_LIMIT;
|
|
|
|
|
$key = 'rate_limit_' . $type;
|
|
|
|
|
$now = time();
|
|
|
|
|
|
|
|
|
|
// Initialize rate limit tracking
|
|
|
|
|
if (!isset($_SESSION[$key])) {
|
|
|
|
|
$_SESSION[$key] = [
|
|
|
|
|
'count' => 0,
|
|
|
|
|
'window_start' => $now
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rateData = &$_SESSION[$key];
|
|
|
|
|
|
|
|
|
|
// Check if window has expired
|
|
|
|
|
if ($now - $rateData['window_start'] >= self::WINDOW_SECONDS) {
|
|
|
|
|
// Reset for new window
|
|
|
|
|
$rateData['count'] = 0;
|
|
|
|
|
$rateData['window_start'] = $now;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Increment request count
|
|
|
|
|
$rateData['count']++;
|
|
|
|
|
|
|
|
|
|
// Check if over limit
|
|
|
|
|
if ($rateData['count'] > $limit) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Apply rate limiting and send error response if exceeded
|
|
|
|
|
*
|
|
|
|
|
* @param string $type 'default' or 'api'
|
|
|
|
|
*/
|
|
|
|
|
public static function apply($type = 'default') {
|
2026-01-28 20:27:15 -05:00
|
|
|
// Periodically clean up old rate limit files (1% chance per request)
|
|
|
|
|
if (mt_rand(1, 100) === 1) {
|
|
|
|
|
self::cleanupOldFiles();
|
|
|
|
|
}
|
|
|
|
|
|
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
|
|
|
if (!self::check($type)) {
|
|
|
|
|
http_response_code(429);
|
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
|
header('Retry-After: ' . self::WINDOW_SECONDS);
|
|
|
|
|
echo json_encode([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'error' => 'Rate limit exceeded. Please try again later.',
|
|
|
|
|
'retry_after' => self::WINDOW_SECONDS
|
|
|
|
|
]);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get current rate limit status
|
|
|
|
|
*
|
|
|
|
|
* @param string $type 'default' or 'api'
|
|
|
|
|
* @return array Rate limit status
|
|
|
|
|
*/
|
|
|
|
|
public static function getStatus($type = 'default') {
|
|
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
|
|
|
session_start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$limit = $type === 'api' ? self::API_LIMIT : self::DEFAULT_LIMIT;
|
|
|
|
|
$key = 'rate_limit_' . $type;
|
|
|
|
|
$now = time();
|
|
|
|
|
|
|
|
|
|
if (!isset($_SESSION[$key])) {
|
|
|
|
|
return [
|
|
|
|
|
'limit' => $limit,
|
|
|
|
|
'remaining' => $limit,
|
|
|
|
|
'reset' => $now + self::WINDOW_SECONDS
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rateData = $_SESSION[$key];
|
|
|
|
|
|
|
|
|
|
// Check if window has expired
|
|
|
|
|
if ($now - $rateData['window_start'] >= self::WINDOW_SECONDS) {
|
|
|
|
|
return [
|
|
|
|
|
'limit' => $limit,
|
|
|
|
|
'remaining' => $limit,
|
|
|
|
|
'reset' => $now + self::WINDOW_SECONDS
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'limit' => $limit,
|
|
|
|
|
'remaining' => max(0, $limit - $rateData['count']),
|
|
|
|
|
'reset' => $rateData['window_start'] + self::WINDOW_SECONDS
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add rate limit headers to response
|
|
|
|
|
*
|
|
|
|
|
* @param string $type 'default' or 'api'
|
|
|
|
|
*/
|
|
|
|
|
public static function addHeaders($type = 'default') {
|
|
|
|
|
$status = self::getStatus($type);
|
|
|
|
|
header('X-RateLimit-Limit: ' . $status['limit']);
|
|
|
|
|
header('X-RateLimit-Remaining: ' . $status['remaining']);
|
|
|
|
|
header('X-RateLimit-Reset: ' . $status['reset']);
|
|
|
|
|
}
|
|
|
|
|
}
|