[FEAT] Add EmailService, RateLimitService, ReportService + integrations

Services:
- EmailService: CSIRT notifications (24h/72h/30d), training alerts, welcome email
- RateLimitService: File-based rate limiting for auth and AI endpoints
- ReportService: Executive HTML report, CSV exports (risks/incidents/controls/assets)

Integrations:
- AuthController: Rate limiting on login (5/min, 20/h) and register (3/10min)
- IncidentController: Email notifications on CSIRT milestones
- AuditController: Executive report and CSV export endpoints
- Router: 429 rate limit error handling, new audit export routes

Database:
- Migration 002: email_log table for notification tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 19:12:46 +01:00
co-authored by Claude Opus 4.6
parent 9aa2788c68
commit 6f4b457ce0
9 changed files with 2383 additions and 0 deletions
@@ -7,6 +7,7 @@
require_once __DIR__ . '/BaseController.php';
require_once APP_PATH . '/services/AIService.php';
require_once APP_PATH . '/services/EmailService.php';
class IncidentController extends BaseController
{
@@ -237,6 +238,9 @@ class IncidentController extends BaseController
'created_by' => $this->getCurrentUserId(),
]);
// Invia notifica email ai responsabili
$this->notifyIncidentStakeholders($id, 'early_warning');
$this->logAudit('early_warning_sent', 'incident', $id);
$this->jsonSuccess(null, 'Early warning registrato');
}
@@ -260,6 +264,9 @@ class IncidentController extends BaseController
'created_by' => $this->getCurrentUserId(),
]);
// Invia notifica email ai responsabili
$this->notifyIncidentStakeholders($id, 'notification');
$this->logAudit('notification_sent', 'incident', $id);
$this->jsonSuccess(null, 'Notifica CSIRT registrata');
}
@@ -283,10 +290,48 @@ class IncidentController extends BaseController
'created_by' => $this->getCurrentUserId(),
]);
// Invia notifica email ai responsabili
$this->notifyIncidentStakeholders($id, 'final_report');
$this->logAudit('final_report_sent', 'incident', $id);
$this->jsonSuccess(null, 'Report finale registrato');
}
/**
* Notifica stakeholder via email per milestone incidente
*/
private function notifyIncidentStakeholders(int $incidentId, string $type): void
{
try {
$incident = Database::fetchOne('SELECT * FROM incidents WHERE id = ?', [$incidentId]);
$org = Database::fetchOne('SELECT * FROM organizations WHERE id = ?', [$this->getCurrentOrgId()]);
if (!$incident || !$org) return;
// Trova org_admin e compliance_manager
$recipients = Database::fetchAll(
'SELECT u.email, u.full_name FROM users u
JOIN user_organizations uo ON uo.user_id = u.id
WHERE uo.organization_id = ? AND uo.role IN ("org_admin", "compliance_manager") AND u.is_active = 1',
[$this->getCurrentOrgId()]
);
if (empty($recipients)) return;
$emailService = new EmailService();
$emails = array_column($recipients, 'email');
match ($type) {
'early_warning' => $emailService->sendIncidentEarlyWarning($incident, $org, $emails),
'notification' => $emailService->sendIncidentNotification($incident, $org, $emails),
'final_report' => $emailService->sendIncidentFinalReport($incident, $org, $emails),
};
} catch (Throwable $e) {
error_log('[EMAIL_ERROR] ' . $e->getMessage());
// Non bloccare il flusso principale per errori email
}
}
/**
* POST /api/incidents/{id}/ai-classify
*/