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>
128 lines
3.6 KiB
PHP
128 lines
3.6 KiB
PHP
<?php
|
|
/**
|
|
* Rate Limiting Middleware
|
|
*
|
|
* Implements session-based rate limiting to prevent abuse.
|
|
*/
|
|
class RateLimitMiddleware {
|
|
// Default limits
|
|
const DEFAULT_LIMIT = 100; // requests per window
|
|
const API_LIMIT = 60; // API requests per window
|
|
const WINDOW_SECONDS = 60; // 1 minute window
|
|
|
|
/**
|
|
* Check rate limit for current request
|
|
*
|
|
* @param string $type 'default' or 'api'
|
|
* @return bool True if request is allowed, false if rate limited
|
|
*/
|
|
public static function check($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();
|
|
|
|
// 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') {
|
|
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']);
|
|
}
|
|
}
|