Fix jwt-token
This commit is contained in:
@@ -116,6 +116,17 @@ async function startServer() {
|
|||||||
console.log('📱 SMS Alerts: ⚠️ Disabled (no Twilio credentials)');
|
console.log('📱 SMS Alerts: ⚠️ Disabled (no Twilio credentials)');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start periodic clear alert checking (every 2 minutes)
|
||||||
|
setInterval(async () => {
|
||||||
|
try {
|
||||||
|
await alertService.checkClearedAlerts();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking cleared alerts:', error);
|
||||||
|
}
|
||||||
|
}, 2 * 60 * 1000); // Check every 2 minutes
|
||||||
|
|
||||||
|
console.log('🔄 Clear alert monitoring: ✅ Started');
|
||||||
|
|
||||||
console.log(`📊 Health check: http://localhost:${PORT}/health`);
|
console.log(`📊 Health check: http://localhost:${PORT}/health`);
|
||||||
console.log(`🌐 API endpoint: http://localhost:${PORT}/api`);
|
console.log(`🌐 API endpoint: http://localhost:${PORT}/api`);
|
||||||
console.log('================================================\n');
|
console.log('================================================\n');
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ module.exports = (sequelize) => {
|
|||||||
defaultValue: ['sms'],
|
defaultValue: ['sms'],
|
||||||
comment: 'Array of alert channels: sms, email, webhook'
|
comment: 'Array of alert channels: sms, email, webhook'
|
||||||
},
|
},
|
||||||
|
sms_phone_number: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
comment: 'Phone number for SMS alerts'
|
||||||
|
},
|
||||||
webhook_url: {
|
webhook_url: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const bcrypt = require('bcryptjs');
|
const bcrypt = require('bcrypt');
|
||||||
const { User, Device } = require('./models');
|
const { User, Device, AlertRule } = require('./models');
|
||||||
|
|
||||||
async function seedDatabase() {
|
async function seedDatabase() {
|
||||||
try {
|
try {
|
||||||
@@ -74,6 +74,65 @@ async function seedDatabase() {
|
|||||||
console.log('✅ Devices already exist');
|
console.log('✅ Devices already exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get admin user for alert rules
|
||||||
|
const adminUser = await User.findOne({ where: { username: 'admin' } });
|
||||||
|
|
||||||
|
// Create location-specific alert rules
|
||||||
|
const alertRuleCount = await AlertRule.count();
|
||||||
|
if (alertRuleCount === 0) {
|
||||||
|
await AlertRule.bulkCreate([
|
||||||
|
{
|
||||||
|
user_id: adminUser.id,
|
||||||
|
name: 'Arlanda Airport Security Alert',
|
||||||
|
description: 'High-priority drone detection alert for Arlanda Airport',
|
||||||
|
is_active: true,
|
||||||
|
device_ids: [1], // Arlanda detector only
|
||||||
|
min_rssi: -85, // Alert on any detection
|
||||||
|
priority: 'critical',
|
||||||
|
alert_channels: ['sms'],
|
||||||
|
sms_phone_number: '0736419592',
|
||||||
|
cooldown_period: 300, // 5 minutes between alerts
|
||||||
|
min_detections: 1,
|
||||||
|
time_window: 60 // 1 minute window
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user_id: adminUser.id,
|
||||||
|
name: 'Muskö Naval Base Security Alert',
|
||||||
|
description: 'High-priority drone detection alert for Muskö Naval Base',
|
||||||
|
is_active: true,
|
||||||
|
device_ids: [2], // Muskö detector only
|
||||||
|
min_rssi: -85, // Alert on any detection
|
||||||
|
priority: 'critical',
|
||||||
|
alert_channels: ['sms'],
|
||||||
|
sms_phone_number: '0739999999',
|
||||||
|
cooldown_period: 300, // 5 minutes between alerts
|
||||||
|
min_detections: 1,
|
||||||
|
time_window: 60 // 1 minute window
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user_id: adminUser.id,
|
||||||
|
name: 'Royal Castle Security Alert',
|
||||||
|
description: 'High-priority drone detection alert for Royal Castle',
|
||||||
|
is_active: true,
|
||||||
|
device_ids: [3], // Royal Castle detector only
|
||||||
|
min_rssi: -85, // Alert on any detection
|
||||||
|
priority: 'critical',
|
||||||
|
alert_channels: ['sms'],
|
||||||
|
sms_phone_number: '0739999999',
|
||||||
|
cooldown_period: 300, // 5 minutes between alerts
|
||||||
|
min_detections: 1,
|
||||||
|
time_window: 60 // 1 minute window
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log('✅ Location-specific alert rules created:');
|
||||||
|
console.log(' - Arlanda Airport → 0736419592');
|
||||||
|
console.log(' - Muskö Naval Base → 073999999');
|
||||||
|
console.log(' - Royal Castle → 073999999');
|
||||||
|
} else {
|
||||||
|
console.log('✅ Alert rules already exist');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('🌱 Database seeding completed');
|
console.log('🌱 Database seeding completed');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Database seeding failed:', error);
|
console.error('❌ Database seeding failed:', error);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class AlertService {
|
|||||||
this.twilioClient = null;
|
this.twilioClient = null;
|
||||||
this.twilioPhone = null;
|
this.twilioPhone = null;
|
||||||
this.twilioEnabled = false;
|
this.twilioEnabled = false;
|
||||||
|
this.activeAlerts = new Map(); // Track active alerts for clear notifications
|
||||||
this.initializeTwilio();
|
this.initializeTwilio();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +157,15 @@ class AlertService {
|
|||||||
for (const rule of alertRules) {
|
for (const rule of alertRules) {
|
||||||
if (await this.shouldTriggerAlert(rule, detection, threatAssessment)) {
|
if (await this.shouldTriggerAlert(rule, detection, threatAssessment)) {
|
||||||
await this.triggerAlert(rule, detection, threatAssessment);
|
await this.triggerAlert(rule, detection, threatAssessment);
|
||||||
|
|
||||||
|
// Track active alert for potential clear notification
|
||||||
|
const alertKey = `${rule.id}-${detection.device_id}`;
|
||||||
|
this.activeAlerts.set(alertKey, {
|
||||||
|
rule,
|
||||||
|
detection,
|
||||||
|
threatAssessment,
|
||||||
|
alertTime: new Date()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +323,7 @@ class AlertService {
|
|||||||
// SECURITY ENHANCEMENT: For critical threats, send to all available channels
|
// SECURITY ENHANCEMENT: For critical threats, send to all available channels
|
||||||
const channels = threatAssessment.level === 'critical'
|
const channels = threatAssessment.level === 'critical'
|
||||||
? ['sms', 'email', 'webhook'] // Force all channels for critical threats
|
? ['sms', 'email', 'webhook'] // Force all channels for critical threats
|
||||||
: rule.actions.channels || ['sms'];
|
: rule.alert_channels || ['sms'];
|
||||||
|
|
||||||
// Send alerts through configured channels
|
// Send alerts through configured channels
|
||||||
for (const channel of channels) {
|
for (const channel of channels) {
|
||||||
@@ -322,8 +332,8 @@ class AlertService {
|
|||||||
try {
|
try {
|
||||||
switch (channel) {
|
switch (channel) {
|
||||||
case 'sms':
|
case 'sms':
|
||||||
if (rule.actions.sms && rule.actions.phone_number) {
|
if (rule.alert_channels.includes('sms') && rule.sms_phone_number) {
|
||||||
alertLog = await this.sendSMSAlert(rule.actions.phone_number, message, rule, detection, threatAssessment);
|
alertLog = await this.sendSMSAlert(rule.sms_phone_number, message, rule, detection, threatAssessment);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -375,7 +385,8 @@ class AlertService {
|
|||||||
// Check if Twilio is enabled
|
// Check if Twilio is enabled
|
||||||
if (!this.twilioEnabled || !this.twilioClient) {
|
if (!this.twilioEnabled || !this.twilioClient) {
|
||||||
console.log('📱 SMS alert skipped - Twilio not configured');
|
console.log('📱 SMS alert skipped - Twilio not configured');
|
||||||
console.log(`📱 Would have sent to ${phoneNumber}: ${message}`);
|
console.log(`📱 Would have sent to ${phoneNumber}:`);
|
||||||
|
console.log(`📱 Message: ${message}`);
|
||||||
|
|
||||||
return await AlertLog.create({
|
return await AlertLog.create({
|
||||||
alert_rule_id: rule.id,
|
alert_rule_id: rule.id,
|
||||||
@@ -392,7 +403,8 @@ class AlertService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`📱 Sending SMS alert to ${phoneNumber}`);
|
console.log(`📱 Sending SMS alert to ${phoneNumber} for rule: ${rule.name}`);
|
||||||
|
console.log(`📱 Message: ${message}`);
|
||||||
|
|
||||||
const twilioMessage = await this.twilioClient.messages.create({
|
const twilioMessage = await this.twilioClient.messages.create({
|
||||||
body: message,
|
body: message,
|
||||||
@@ -400,7 +412,7 @@ class AlertService {
|
|||||||
to: phoneNumber
|
to: phoneNumber
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`✅ SMS sent successfully: ${twilioMessage.sid}`);
|
console.log(`✅ SMS sent successfully to ${phoneNumber}: ${twilioMessage.sid}`);
|
||||||
|
|
||||||
return await AlertLog.create({
|
return await AlertLog.create({
|
||||||
alert_rule_id: rule.id,
|
alert_rule_id: rule.id,
|
||||||
@@ -414,7 +426,7 @@ class AlertService {
|
|||||||
priority: rule.priority
|
priority: rule.priority
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to send SMS:', error.message);
|
console.error(`❌ Failed to send SMS to ${phoneNumber}:`, error.message);
|
||||||
|
|
||||||
return await AlertLog.create({
|
return await AlertLog.create({
|
||||||
alert_rule_id: rule.id,
|
alert_rule_id: rule.id,
|
||||||
@@ -553,6 +565,59 @@ class AlertService {
|
|||||||
|
|
||||||
return hours * 60 + minutes;
|
return hours * 60 + minutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for cleared alerts (call this periodically)
|
||||||
|
async checkClearedAlerts() {
|
||||||
|
try {
|
||||||
|
const now = new Date();
|
||||||
|
const clearThreshold = 5 * 60 * 1000; // 5 minutes without detection = cleared
|
||||||
|
|
||||||
|
for (const [alertKey, alertData] of this.activeAlerts.entries()) {
|
||||||
|
const timeSinceAlert = now - alertData.alertTime;
|
||||||
|
|
||||||
|
if (timeSinceAlert > clearThreshold) {
|
||||||
|
// Check if there are any recent detections from this device
|
||||||
|
const recentDetections = await DroneDetection.count({
|
||||||
|
where: {
|
||||||
|
device_id: alertData.detection.device_id,
|
||||||
|
device_timestamp: {
|
||||||
|
[Op.gte]: new Date(now - clearThreshold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (recentDetections === 0) {
|
||||||
|
await this.sendClearAlert(alertData);
|
||||||
|
this.activeAlerts.delete(alertKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking cleared alerts:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendClearAlert(alertData) {
|
||||||
|
const { rule, detection } = alertData;
|
||||||
|
|
||||||
|
if (rule.alert_channels.includes('sms') && rule.sms_phone_number) {
|
||||||
|
const device = await Device.findByPk(detection.device_id);
|
||||||
|
const clearMessage = this.generateClearMessage(device, rule);
|
||||||
|
|
||||||
|
console.log(`🟢 ALERT CLEARED - Sending clear notification for ${device.name} to ${rule.sms_phone_number}`);
|
||||||
|
|
||||||
|
await this.sendSMSAlert(rule.sms_phone_number, clearMessage, rule, detection, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateClearMessage(device, rule) {
|
||||||
|
return `🟢 ALL CLEAR 🟢\n\n` +
|
||||||
|
`📍 LOCATION: ${device.location_description || device.name}\n` +
|
||||||
|
`🔧 DEVICE: ${device.name}\n` +
|
||||||
|
`⏰ TIME: ${new Date().toLocaleString('sv-SE')}\n\n` +
|
||||||
|
`✅ No drone activity detected for 5+ minutes.\n` +
|
||||||
|
`🛡️ Area is secure.`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export the class directly (not a singleton instance)
|
// Export the class directly (not a singleton instance)
|
||||||
|
|||||||
Reference in New Issue
Block a user