From 7a6a2edd563c5fda4b40ed9af7b127a48f410f08 Mon Sep 17 00:00:00 2001 From: Alexander Borg Date: Thu, 28 Aug 2025 08:24:20 +0200 Subject: [PATCH] Fix jwt-token --- client/src/pages/Devices.jsx | 18 +++ client/src/services/api.js | 15 +- server/routes/device.js | 7 +- server/routes/devices.js | 293 ----------------------------------- 4 files changed, 34 insertions(+), 299 deletions(-) delete mode 100644 server/routes/devices.js diff --git a/client/src/pages/Devices.jsx b/client/src/pages/Devices.jsx index 9666813..9167ad1 100644 --- a/client/src/pages/Devices.jsx +++ b/client/src/pages/Devices.jsx @@ -51,6 +51,11 @@ const Devices = () => { fetchDevices(); } catch (error) { console.error('Error approving device:', error); + if (error.response?.status === 401 || error.response?.status === 403) { + alert('Your session has expired. Please log in again.'); + return; + } + alert('Error approving device: ' + (error.response?.data?.message || error.message)); } }; @@ -61,6 +66,11 @@ const Devices = () => { fetchDevices(); } catch (error) { console.error('Error rejecting device:', error); + if (error.response?.status === 401 || error.response?.status === 403) { + alert('Your session has expired. Please log in again.'); + return; + } + alert('Error rejecting device: ' + (error.response?.data?.message || error.message)); } } }; @@ -559,6 +569,14 @@ const DeviceModal = ({ device, onClose, onSave }) => { onSave(); } catch (error) { console.error('Error saving device:', error); + + // Check if it's a token expiration error + if (error.response?.status === 401 || error.response?.status === 403) { + alert('Your session has expired. Please log in again.'); + // The API interceptor will handle the logout automatically + return; + } + alert('Error saving device: ' + (error.response?.data?.message || error.message)); } finally { setSaving(false); diff --git a/client/src/services/api.js b/client/src/services/api.js index 2b5e835..14a0282 100644 --- a/client/src/services/api.js +++ b/client/src/services/api.js @@ -48,11 +48,16 @@ api.interceptors.request.use( api.interceptors.response.use( (response) => response, (error) => { - if (error.response?.status === 401) { - // Token expired or invalid - remove token and let ProtectedRoute handle navigation - localStorage.removeItem('token'); - // Force a state update by dispatching a custom event - window.dispatchEvent(new CustomEvent('auth-logout')); + if (error.response?.status === 401 || error.response?.status === 403) { + // Check if it's a token-related error + const errorMessage = error.response?.data?.message || ''; + if (errorMessage.includes('token') || errorMessage.includes('expired') || error.response?.status === 401) { + console.warn('🔐 Token expired or invalid - logging out'); + // Token expired or invalid - remove token and let ProtectedRoute handle navigation + localStorage.removeItem('token'); + // Force a state update by dispatching a custom event + window.dispatchEvent(new CustomEvent('auth-logout')); + } } return Promise.reject(error); } diff --git a/server/routes/device.js b/server/routes/device.js index 0b6e8d5..db71c61 100644 --- a/server/routes/device.js +++ b/server/routes/device.js @@ -252,7 +252,7 @@ router.post('/', authenticateToken, validateRequest(deviceSchema), async (req, r } }); -// PUT /api/devices/:id - Update device (admin only) +// PUT /api/devices/:id - Update device router.put('/:id', authenticateToken, validateRequest(updateDeviceSchema), async (req, res) => { try { const device = await Device.findByPk(req.params.id); @@ -264,11 +264,16 @@ router.put('/:id', authenticateToken, validateRequest(updateDeviceSchema), async }); } + console.log(`📝 Device ${req.params.id} update requested by user ${req.user.id} (${req.user.username})`); + console.log('Update data:', req.body); + await device.update(req.body); // Emit real-time update req.io.emit('device_updated', device); + console.log(`✅ Device ${req.params.id} updated successfully`); + res.json({ success: true, data: device, diff --git a/server/routes/devices.js b/server/routes/devices.js deleted file mode 100644 index b1b15db..0000000 --- a/server/routes/devices.js +++ /dev/null @@ -1,293 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const Joi = require('joi'); -const { validateRequest } = require('../middleware/validation'); -const { Device, Heartbeat, DroneDetection } = require('../models'); -const { Op } = require('sequelize'); - -// Validation schemas -const approveDeviceSchema = Joi.object({ - device_id: Joi.number().integer().required(), - approved: Joi.boolean().required() -}); - -const updateDeviceSchema = Joi.object({ - name: Joi.string().optional(), - geo_lat: Joi.number().min(-90).max(90).optional(), - geo_lon: Joi.number().min(-180).max(180).optional(), - location_description: Joi.string().optional(), - is_active: Joi.boolean().optional(), - heartbeat_interval: Joi.number().integer().min(30).optional(), - notes: Joi.string().allow('').optional() -}); - -// GET /api/devices - List all devices with approval status -router.get('/', async (req, res) => { - try { - const devices = await Device.findAll({ - attributes: [ - 'id', 'name', 'geo_lat', 'geo_lon', 'location_description', - 'is_active', 'is_approved', 'last_heartbeat', 'heartbeat_interval', - 'firmware_version', 'installation_date', 'notes', 'created_at' - ], - order: [['created_at', 'DESC']] - }); - - // Add status information - const devicesWithStatus = devices.map(device => { - const lastHeartbeat = device.last_heartbeat; - const heartbeatInterval = device.heartbeat_interval || 300; - const now = new Date(); - const timeSinceHeartbeat = lastHeartbeat ? - (now - new Date(lastHeartbeat)) / 1000 : null; - - let status = 'unknown'; - if (!device.is_approved) { - status = 'pending_approval'; - } else if (!device.is_active) { - status = 'inactive'; - } else if (timeSinceHeartbeat && timeSinceHeartbeat > heartbeatInterval * 2) { - status = 'offline'; - } else if (timeSinceHeartbeat && timeSinceHeartbeat <= heartbeatInterval * 2) { - status = 'online'; - } - - return { - ...device.toJSON(), - status, - time_since_heartbeat: timeSinceHeartbeat - }; - }); - - res.json({ - success: true, - data: devicesWithStatus - }); - } catch (error) { - console.error('Error fetching devices:', error); - res.status(500).json({ - success: false, - error: 'Failed to fetch devices' - }); - } -}); - -// GET /api/devices/pending - List devices pending approval -router.get('/pending', async (req, res) => { - try { - const pendingDevices = await Device.findAll({ - where: { is_approved: false }, - attributes: [ - 'id', 'name', 'geo_lat', 'geo_lon', 'last_heartbeat', - 'created_at', 'firmware_version' - ], - order: [['created_at', 'DESC']] - }); - - res.json({ - success: true, - data: pendingDevices - }); - } catch (error) { - console.error('Error fetching pending devices:', error); - res.status(500).json({ - success: false, - error: 'Failed to fetch pending devices' - }); - } -}); - -// POST /api/devices/:id/approve - Approve or reject a device -router.post('/:id/approve', validateRequest(approveDeviceSchema), async (req, res) => { - try { - const deviceId = parseInt(req.params.id); - const { approved } = req.body; - - const device = await Device.findByPk(deviceId); - if (!device) { - return res.status(404).json({ - success: false, - error: 'Device not found' - }); - } - - await device.update({ is_approved: approved }); - - // Emit real-time notification - const { io } = require('../index'); - if (io) { - io.emit('device_approval_updated', { - device_id: deviceId, - approved: approved, - timestamp: new Date().toISOString(), - message: approved ? - `Device ${deviceId} has been approved` : - `Device ${deviceId} approval has been revoked` - }); - } - - console.log(`${approved ? '✅' : '❌'} Device ${deviceId} approval ${approved ? 'granted' : 'revoked'}`); - - res.json({ - success: true, - data: device, - message: approved ? 'Device approved successfully' : 'Device approval revoked' - }); - } catch (error) { - console.error('Error updating device approval:', error); - res.status(500).json({ - success: false, - error: 'Failed to update device approval' - }); - } -}); - -// PUT /api/devices/:id - Update device information -router.put('/:id', validateRequest(updateDeviceSchema), async (req, res) => { - try { - const deviceId = parseInt(req.params.id); - const updateData = req.body; - - const device = await Device.findByPk(deviceId); - if (!device) { - return res.status(404).json({ - success: false, - error: 'Device not found' - }); - } - - await device.update(updateData); - - // Emit real-time notification - const { io } = require('../index'); - if (io) { - io.emit('device_updated', { - device_id: deviceId, - updates: updateData, - timestamp: new Date().toISOString() - }); - } - - console.log(`📝 Device ${deviceId} updated`); - - res.json({ - success: true, - data: device, - message: 'Device updated successfully' - }); - } catch (error) { - console.error('Error updating device:', error); - res.status(500).json({ - success: false, - error: 'Failed to update device' - }); - } -}); - -// DELETE /api/devices/:id - Delete a device and all its data -router.delete('/:id', async (req, res) => { - try { - const deviceId = parseInt(req.params.id); - - const device = await Device.findByPk(deviceId); - if (!device) { - return res.status(404).json({ - success: false, - error: 'Device not found' - }); - } - - // Delete related data first (due to foreign key constraints) - await DroneDetection.destroy({ where: { device_id: deviceId } }); - await Heartbeat.destroy({ where: { device_id: deviceId } }); - - // Delete the device - await device.destroy(); - - // Emit real-time notification - const { io } = require('../index'); - if (io) { - io.emit('device_deleted', { - device_id: deviceId, - timestamp: new Date().toISOString(), - message: `Device ${deviceId} has been deleted` - }); - } - - console.log(`🗑️ Device ${deviceId} and all related data deleted`); - - res.json({ - success: true, - message: 'Device deleted successfully' - }); - } catch (error) { - console.error('Error deleting device:', error); - res.status(500).json({ - success: false, - error: 'Failed to delete device' - }); - } -}); - -// GET /api/devices/:id/stats - Get device statistics -router.get('/:id/stats', async (req, res) => { - try { - const deviceId = parseInt(req.params.id); - const days = parseInt(req.query.days) || 7; - - const device = await Device.findByPk(deviceId); - if (!device) { - return res.status(404).json({ - success: false, - error: 'Device not found' - }); - } - - const startDate = new Date(); - startDate.setDate(startDate.getDate() - days); - - // Get heartbeat count - const heartbeatCount = await Heartbeat.count({ - where: { - device_id: deviceId, - received_at: { [Op.gte]: startDate } - } - }); - - // Get detection count - const detectionCount = await DroneDetection.count({ - where: { - device_id: deviceId, - server_timestamp: { [Op.gte]: startDate } - } - }); - - // Get latest heartbeat - const latestHeartbeat = await Heartbeat.findOne({ - where: { device_id: deviceId }, - order: [['received_at', 'DESC']], - attributes: ['received_at', 'signal_strength', 'battery_level', 'temperature'] - }); - - res.json({ - success: true, - data: { - device_id: deviceId, - period_days: days, - heartbeat_count: heartbeatCount, - detection_count: detectionCount, - latest_heartbeat: latestHeartbeat, - uptime_percentage: heartbeatCount > 0 ? - Math.min(100, (heartbeatCount / (days * 24 * 12)) * 100) : 0 // Assuming 5min intervals - } - }); - } catch (error) { - console.error('Error fetching device stats:', error); - res.status(500).json({ - success: false, - error: 'Failed to fetch device statistics' - }); - } -}); - -module.exports = router;