Initial commit

This commit is contained in:
2025-08-16 19:43:44 +02:00
commit ea9a2627b4
64 changed files with 9232 additions and 0 deletions

View File

@@ -0,0 +1,499 @@
const twilio = require('twilio');
const { AlertRule, AlertLog, User, Device } = require('../models');
const { Op } = require('sequelize');
class AlertService {
constructor() {
this.twilioClient = null;
this.initializeTwilio();
}
// RSSI-based threat assessment for security installations
assessThreatLevel(rssi, droneType) {
// RSSI typically ranges from -30 (very close) to -100 (very far)
// For 15km range detection, we need to establish threat zones for sensitive facilities
let threatLevel = 'low';
let estimatedDistance = 0;
let description = '';
let actionRequired = false;
// Convert RSSI to estimated distance (rough calculation)
// Formula: Distance (m) = 10^((RSSI_at_1m - RSSI) / (10 * n))
// Where n = path loss exponent (typically 2-4 for outdoor environments)
const rssiAt1m = -30; // Typical RSSI at 1 meter
const pathLossExponent = 3; // Outdoor environment with obstacles
estimatedDistance = Math.pow(10, (rssiAt1m - rssi) / (10 * pathLossExponent));
// Threat level assessment based on distance zones for sensitive facilities
if (rssi >= -40) {
// Very close: 0-50 meters - CRITICAL THREAT
threatLevel = 'critical';
description = 'IMMEDIATE THREAT: Drone within security perimeter (0-50m)';
actionRequired = true;
} else if (rssi >= -55) {
// Close: 50-200 meters - HIGH THREAT
threatLevel = 'high';
description = 'HIGH THREAT: Drone approaching facility (50-200m)';
actionRequired = true;
} else if (rssi >= -70) {
// Medium: 200-1000 meters - MEDIUM THREAT
threatLevel = 'medium';
description = 'MEDIUM THREAT: Drone in facility vicinity (200m-1km)';
actionRequired = false;
} else if (rssi >= -85) {
// Far: 1-5 kilometers - LOW THREAT
threatLevel = 'low';
description = 'LOW THREAT: Drone detected at distance (1-5km)';
actionRequired = false;
} else {
// Very far: 5-15 kilometers - MONITORING ONLY
threatLevel = 'monitoring';
description = 'MONITORING: Drone detected at long range (5-15km)';
actionRequired = false;
}
// Adjust threat level based on drone type (if classified)
const droneTypes = {
0: 'Consumer/Hobby',
1: 'Professional/Military',
2: 'Racing/High-speed',
3: 'Unknown/Custom'
};
if (droneType === 1) {
// Military/Professional drone - escalate threat
if (threatLevel === 'low') threatLevel = 'medium';
if (threatLevel === 'medium') threatLevel = 'high';
if (threatLevel === 'high') threatLevel = 'critical';
description += ' - PROFESSIONAL/MILITARY DRONE DETECTED';
actionRequired = true;
} else if (droneType === 2) {
// Racing/Fast drone - escalate if close
if (rssi >= -55 && threatLevel !== 'critical') {
threatLevel = 'high';
description += ' - HIGH-SPEED DRONE DETECTED';
actionRequired = true;
}
}
return {
level: threatLevel,
estimatedDistance: Math.round(estimatedDistance),
rssi,
droneType: droneTypes[droneType] || 'Unknown',
description,
requiresImmediateAction: actionRequired,
priority: threatLevel === 'critical' ? 1 : threatLevel === 'high' ? 2 : threatLevel === 'medium' ? 3 : 4
};
}
initializeTwilio() {
if (process.env.TWILIO_ACCOUNT_SID && process.env.TWILIO_AUTH_TOKEN) {
this.twilioClient = twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
);
} else {
console.warn('Twilio credentials not configured. SMS alerts will be disabled.');
}
}
async processAlert(detection) {
try {
console.log(`🔍 Processing alert for detection ${detection.id}`);
// Assess threat level based on RSSI and drone type
const threatAssessment = this.assessThreatLevel(detection.rssi, detection.drone_type);
console.log('⚠️ Threat assessment:', threatAssessment);
// Update detection with threat assessment
await detection.update({
processed: true,
threat_level: threatAssessment.level,
estimated_distance: threatAssessment.estimatedDistance
});
// Get all active alert rules
const alertRules = await AlertRule.findAll({
where: { is_active: true },
include: [{
model: User,
as: 'user',
where: { is_active: true }
}]
});
for (const rule of alertRules) {
if (await this.shouldTriggerAlert(rule, detection, threatAssessment)) {
await this.triggerAlert(rule, detection, threatAssessment);
}
}
} catch (error) {
console.error('Error processing alert:', error);
throw error;
}
}
async shouldTriggerAlert(rule, detection, threatAssessment) {
try {
// SECURITY ENHANCEMENT: Check threat level requirements
if (rule.conditions.min_threat_level) {
const threatLevels = { 'monitoring': 0, 'low': 1, 'medium': 2, 'high': 3, 'critical': 4 };
const requiredLevel = threatLevels[rule.conditions.min_threat_level] || 0;
const currentLevel = threatLevels[threatAssessment.level] || 0;
if (currentLevel < requiredLevel) {
console.log(`Alert rule ${rule.name}: Threat level ${threatAssessment.level} below minimum ${rule.conditions.min_threat_level}`);
return false;
}
}
// SECURITY ENHANCEMENT: For government/sensitive sites, always alert on critical threats
if (threatAssessment.level === 'critical') {
console.log(`🚨 CRITICAL THREAT DETECTED - Force triggering alert for rule ${rule.name}`);
return true;
}
// Check device filter
if (rule.conditions.device_ids && rule.conditions.device_ids.length > 0 &&
!rule.conditions.device_ids.includes(detection.device_id)) {
return false;
}
// Check drone type filter
if (rule.conditions.drone_types && rule.conditions.drone_types.length > 0 &&
!rule.conditions.drone_types.includes(detection.drone_type)) {
return false;
}
// Check RSSI thresholds (enhanced for security)
if (rule.conditions.rssi_threshold && detection.rssi < rule.conditions.rssi_threshold) {
return false;
}
// SECURITY ENHANCEMENT: Check estimated distance
if (rule.conditions.max_distance && threatAssessment.estimatedDistance > rule.conditions.max_distance) {
console.log(`Alert rule ${rule.name}: Distance ${threatAssessment.estimatedDistance}m exceeds maximum ${rule.conditions.max_distance}m`);
return false;
}
// Check frequency ranges
if (rule.frequency_ranges && rule.frequency_ranges.length > 0) {
const inRange = rule.frequency_ranges.some(range =>
detection.freq >= range.min && detection.freq <= range.max
);
if (!inRange) {
return false;
}
}
// Check time window and minimum detections
if (rule.min_detections > 1) {
const timeWindowStart = new Date(Date.now() - rule.time_window * 1000);
const recentDetections = await DroneDetection.count({
where: {
device_id: detection.device_id,
drone_id: detection.drone_id,
server_timestamp: {
[Op.gte]: timeWindowStart
}
}
});
if (recentDetections < rule.min_detections) {
return false;
}
}
// Check cooldown period
if (rule.cooldown_period > 0) {
const cooldownStart = new Date(Date.now() - rule.cooldown_period * 1000);
const recentAlert = await AlertLog.findOne({
where: {
alert_rule_id: rule.id,
status: 'sent',
sent_at: {
[Op.gte]: cooldownStart
}
},
include: [{
model: DroneDetection,
as: 'detection',
where: {
device_id: detection.device_id,
drone_id: detection.drone_id
}
}]
});
if (recentAlert) {
return false;
}
}
// Check active hours and days
if (rule.active_hours || rule.active_days) {
const now = new Date();
const currentHour = now.getHours();
const currentMinute = now.getMinutes();
const currentTime = currentHour * 60 + currentMinute;
const currentDay = now.getDay() || 7; // Convert Sunday from 0 to 7
if (rule.active_days && !rule.active_days.includes(currentDay)) {
return false;
}
if (rule.active_hours) {
const startTime = this.parseTime(rule.active_hours.start);
const endTime = this.parseTime(rule.active_hours.end);
if (startTime !== null && endTime !== null) {
if (startTime <= endTime) {
// Same day range
if (currentTime < startTime || currentTime > endTime) {
return false;
}
} else {
// Overnight range
if (currentTime < startTime && currentTime > endTime) {
return false;
}
}
}
}
}
return true;
} catch (error) {
console.error('Error checking alert conditions:', error);
return false;
}
}
async triggerAlert(rule, detection, threatAssessment) {
try {
const user = rule.user;
const device = await Device.findByPk(detection.device_id);
// Generate enhanced alert message with threat assessment
const message = this.generateSecurityAlertMessage(detection, device, rule, threatAssessment);
// SECURITY ENHANCEMENT: For critical threats, send to all available channels
const channels = threatAssessment.level === 'critical'
? ['sms', 'email', 'webhook'] // Force all channels for critical threats
: rule.actions.channels || ['sms'];
// Send alerts through configured channels
for (const channel of channels) {
let alertLog = null;
try {
switch (channel) {
case 'sms':
if (rule.actions.sms && rule.actions.phone_number) {
alertLog = await this.sendSMSAlert(rule.actions.phone_number, message, rule, detection, threatAssessment);
}
break;
case 'email':
if (rule.actions.email && user.email) {
alertLog = await this.sendEmailAlert(user.email, message, rule, detection, threatAssessment);
}
break;
case 'webhook':
if (rule.actions.webhook_url) {
alertLog = await this.sendWebhookAlert(rule.actions.webhook_url, detection, device, rule, threatAssessment);
}
break;
default:
console.warn(`Unknown alert channel: ${channel}`);
}
if (alertLog) {
console.log(`🚨 ${threatAssessment.level.toUpperCase()} THREAT: Alert sent via ${channel} for detection ${detection.id}`);
}
} catch (channelError) {
console.error(`Error sending ${channel} alert:`, channelError);
// Log failed alert
await AlertLog.create({
alert_rule_id: rule.id,
detection_id: detection.id,
alert_type: channel,
recipient: channel === 'sms' ? user.phone_number :
channel === 'email' ? user.email : rule.webhook_url,
message: message,
status: 'failed',
error_message: channelError.message,
priority: rule.priority
});
}
}
} catch (error) {
console.error('Error triggering alert:', error);
throw error;
}
}
async sendSMSAlert(phoneNumber, message, rule, detection) {
if (!this.twilioClient) {
throw new Error('Twilio not configured');
}
const twilioMessage = await this.twilioClient.messages.create({
body: message,
from: process.env.TWILIO_PHONE_NUMBER,
to: phoneNumber
});
return await AlertLog.create({
alert_rule_id: rule.id,
detection_id: detection.id,
alert_type: 'sms',
recipient: phoneNumber,
message: message,
status: 'sent',
sent_at: new Date(),
external_id: twilioMessage.sid,
priority: rule.priority
});
}
async sendEmailAlert(email, message, rule, detection) {
// Email implementation would go here
// For now, just log the alert
console.log(`Email alert would be sent to ${email}: ${message}`);
return await AlertLog.create({
alert_rule_id: rule.id,
detection_id: detection.id,
alert_type: 'email',
recipient: email,
message: message,
status: 'sent',
sent_at: new Date(),
priority: rule.priority
});
}
async sendWebhookAlert(webhookUrl, detection, device, rule) {
const payload = {
event: 'drone_detection',
timestamp: new Date().toISOString(),
detection: {
id: detection.id,
device_id: detection.device_id,
drone_id: detection.drone_id,
drone_type: detection.drone_type,
rssi: detection.rssi,
freq: detection.freq,
geo_lat: detection.geo_lat,
geo_lon: detection.geo_lon,
server_timestamp: detection.server_timestamp
},
device: {
id: device.id,
name: device.name,
geo_lat: device.geo_lat,
geo_lon: device.geo_lon,
location_description: device.location_description
},
alert_rule: {
id: rule.id,
name: rule.name,
priority: rule.priority
}
};
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'DroneDetectionSystem/1.0'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Webhook failed with status ${response.status}`);
}
return await AlertLog.create({
alert_rule_id: rule.id,
detection_id: detection.id,
alert_type: 'webhook',
recipient: webhookUrl,
message: JSON.stringify(payload),
status: 'sent',
sent_at: new Date(),
priority: rule.priority
});
}
generateAlertMessage(detection, device, rule) {
const deviceName = device.name || `Device ${device.id}`;
const location = device.location_description ||
`${device.geo_lat}, ${device.geo_lon}` ||
'Unknown location';
return `🚨 DRONE ALERT: Drone detected by ${deviceName} at ${location}. ` +
`Drone ID: ${detection.drone_id}, Frequency: ${detection.freq}MHz, ` +
`RSSI: ${detection.rssi}dBm. Time: ${new Date().toLocaleString()}`;
}
// SECURITY ENHANCEMENT: Enhanced message generation with threat assessment
generateSecurityAlertMessage(detection, device, rule, threatAssessment) {
const timestamp = new Date().toLocaleString('sv-SE', { timeZone: 'Europe/Stockholm' });
const deviceName = device ? device.name : `Device ${detection.device_id}`;
const location = device ? device.location : 'Unknown location';
// Create security-focused alert message
let message = `🚨 SECURITY ALERT 🚨\n`;
message += `THREAT LEVEL: ${threatAssessment.level.toUpperCase()}\n`;
message += `${threatAssessment.description}\n\n`;
message += `📍 LOCATION: ${location}\n`;
message += `🔧 DEVICE: ${deviceName}\n`;
message += `📏 DISTANCE: ~${threatAssessment.estimatedDistance}m\n`;
message += `📶 SIGNAL: ${detection.rssi} dBm\n`;
message += `🚁 DRONE TYPE: ${threatAssessment.droneType}\n`;
message += `⏰ TIME: ${timestamp}\n`;
if (threatAssessment.requiresImmediateAction) {
message += `\n⚠️ IMMEDIATE ACTION REQUIRED\n`;
message += `Security personnel should respond immediately.`;
}
return message;
}
parseTime(timeString) {
if (!timeString) return null;
const match = timeString.match(/^(\d{1,2}):(\d{2})$/);
if (!match) return null;
const hours = parseInt(match[1]);
const minutes = parseInt(match[2]);
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
return null;
}
return hours * 60 + minutes;
}
}
// Export singleton instance
const alertService = new AlertService();
module.exports = {
processAlert: (detection) => alertService.processAlert(detection),
AlertService
};