Fix jwt-token

This commit is contained in:
2025-09-17 22:32:55 +02:00
parent 6c74c7c524
commit 43b548c05a
5 changed files with 77 additions and 19 deletions

View File

@@ -37,6 +37,51 @@ router.post('/devices/:id/heartbeat', async (req, res) => {
const { Device, Heartbeat } = models; const { Device, Heartbeat } = models;
const deviceId = parseInt(req.params.id); const deviceId = parseInt(req.params.id);
// Validate heartbeat payload
const { type, key, status, cpu_usage, memory_usage, disk_usage, uptime, firmware_version, timestamp } = req.body;
// Check for unexpected fields
const allowedFields = ['type', 'key', 'status', 'cpu_usage', 'memory_usage', 'disk_usage', 'uptime', 'firmware_version', 'timestamp'];
const receivedFields = Object.keys(req.body);
const unexpectedFields = receivedFields.filter(field => !allowedFields.includes(field));
if (unexpectedFields.length > 0) {
return res.status(400).json({
success: false,
message: `Unexpected fields: ${unexpectedFields.join(', ')}`
});
}
// Validate status if provided
if (status && !['online', 'offline', 'error', 'maintenance'].includes(status)) {
return res.status(400).json({
success: false,
message: 'Invalid status. Must be one of: online, offline, error, maintenance'
});
}
// Validate percentage fields
const percentageFields = ['cpu_usage', 'memory_usage', 'disk_usage'];
for (const field of percentageFields) {
if (req.body[field] !== undefined) {
const value = parseFloat(req.body[field]);
if (isNaN(value) || value < 0 || value > 100) {
return res.status(400).json({
success: false,
message: `${field} must be a number between 0 and 100`
});
}
}
}
// Validate timestamp if provided
if (timestamp && isNaN(Date.parse(timestamp))) {
return res.status(400).json({
success: false,
message: 'Invalid timestamp format'
});
}
// Find the device // Find the device
const device = await Device.findByPk(deviceId); const device = await Device.findByPk(deviceId);
if (!device) { if (!device) {
@@ -50,12 +95,13 @@ router.post('/devices/:id/heartbeat', async (req, res) => {
if (!device.is_approved) { if (!device.is_approved) {
return res.status(403).json({ return res.status(403).json({
success: false, success: false,
message: 'Device not approved for heartbeat reporting' message: 'Device not approved for heartbeat reporting',
approval_required: true
}); });
} }
// Extract heartbeat data - handle both simple device heartbeats and detailed health reports // Extract heartbeat data - handle both simple device heartbeats and detailed health reports
const { type, key, status, cpu_usage, memory_usage, disk_usage, uptime, firmware_version } = req.body; // Variables already destructured above: type, key, status, cpu_usage, memory_usage, disk_usage, uptime, firmware_version
// For simple device heartbeats: {type:"heartbeat", key:"unique device ID"} // For simple device heartbeats: {type:"heartbeat", key:"unique device ID"}
// For detailed health reports: {status:"online", cpu_usage:25.5, memory_usage:60.2, etc.} // For detailed health reports: {status:"online", cpu_usage:25.5, memory_usage:60.2, etc.}
@@ -486,6 +532,16 @@ router.get('/metrics', async (req, res) => {
const models = getModels(); const models = getModels();
const { sequelize } = models; const { sequelize } = models;
// Get user tenant ID with proper fallback
const tenantId = req.user?.tenant_id || req.tenantId || req.tenant?.id;
if (!tenantId) {
return res.status(400).json({
status: 'error',
message: 'No tenant context available'
});
}
// Get system metrics // Get system metrics
const memUsage = process.memoryUsage(); const memUsage = process.memoryUsage();
const startTime = Date.now(); const startTime = Date.now();
@@ -501,7 +557,7 @@ router.get('/metrics', async (req, res) => {
const [deviceCount] = await sequelize.query( const [deviceCount] = await sequelize.query(
'SELECT COUNT(*) as count FROM "Devices" WHERE tenant_id = :tenantId', 'SELECT COUNT(*) as count FROM "Devices" WHERE tenant_id = :tenantId',
{ {
replacements: { tenantId: req.user.tenant_id }, replacements: { tenantId: tenantId },
type: sequelize.QueryTypes.SELECT type: sequelize.QueryTypes.SELECT
} }
); );
@@ -509,7 +565,7 @@ router.get('/metrics', async (req, res) => {
const [detectionCount] = await sequelize.query( const [detectionCount] = await sequelize.query(
'SELECT COUNT(*) as count FROM "DroneDetections" WHERE tenant_id = :tenantId', 'SELECT COUNT(*) as count FROM "DroneDetections" WHERE tenant_id = :tenantId',
{ {
replacements: { tenantId: req.user.tenant_id }, replacements: { tenantId: tenantId },
type: sequelize.QueryTypes.SELECT type: sequelize.QueryTypes.SELECT
} }
); );

View File

@@ -392,7 +392,7 @@ describe('Models', () => {
}); });
expect(detectionWithDevice.device).to.exist; expect(detectionWithDevice.device).to.exist;
expect(detectionWithDevice.device.id).to.equal(device.id); expect(detectionWithDevice.device.id).to.equal(String(device.id));
}); });
}); });
@@ -537,7 +537,7 @@ describe('Models', () => {
}); });
expect(logWithDevice.device).to.exist; expect(logWithDevice.device).to.exist;
expect(logWithDevice.device.id).to.equal(device.id); expect(logWithDevice.device.id).to.equal(String(device.id));
}); });
}); });

View File

@@ -139,7 +139,7 @@ describe('Detections Routes', () => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
expect(response.body.data.detections).to.have.length(1); expect(response.body.data.detections).to.have.length(1);
expect(response.body.data.detections[0].device_id).to.equal(device1.id); expect(response.body.data.detections[0].device_id).to.equal(String(device1.id));
}); });
it('should support filtering by drone type', async () => { it('should support filtering by drone type', async () => {

View File

@@ -65,7 +65,7 @@ describe('Device Routes', () => {
expect(response.body.data).to.have.length(2); expect(response.body.data).to.have.length(2);
const deviceIds = response.body.data.map(d => d.id); const deviceIds = response.body.data.map(d => d.id);
expect(deviceIds).to.include.members([123, 124]); expect(deviceIds).to.include.members(['123', '124']);
}); });
it('should only return devices for user tenant', async () => { it('should only return devices for user tenant', async () => {
@@ -85,7 +85,7 @@ describe('Device Routes', () => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
expect(response.body.data).to.have.length(1); expect(response.body.data).to.have.length(1);
expect(response.body.data[0].id).to.equal(111); expect(response.body.data[0].id).to.equal('111');
}); });
it('should require authentication', async () => { it('should require authentication', async () => {
@@ -155,7 +155,7 @@ describe('Device Routes', () => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
expect(response.body.success).to.be.true; expect(response.body.success).to.be.true;
expect(response.body.data.id).to.equal(12345); expect(response.body.data.id).to.equal('12345');
expect(response.body.data.name).to.equal('Specific Device'); expect(response.body.data.name).to.equal('Specific Device');
expect(response.body.data.location_description).to.equal('Test Location'); expect(response.body.data.location_description).to.equal('Test Location');
}); });
@@ -570,18 +570,20 @@ describe('Device Routes', () => {
}); });
const response = await request(app) const response = await request(app)
.get('/devices') .get('/devices?include_stats=true')
.set('Authorization', `Bearer ${token}`); .set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
const devices = response.body.data; const devices = response.body.data;
const online = devices.find(d => d.id === onlineDevice.id); const online = devices.find(d => d.id === String(onlineDevice.id));
const offline = devices.find(d => d.id === offlineDevice.id); const offline = devices.find(d => d.id === String(offlineDevice.id));
// These assertions depend on your business logic for determining online status // These assertions depend on your business logic for determining online status
expect(online).to.exist; expect(online).to.exist;
expect(online.stats).to.exist;
expect(offline).to.exist; expect(offline).to.exist;
expect(offline.stats).to.exist;
}); });
}); });
}); });

View File

@@ -244,7 +244,7 @@ describe('Healthcheck Routes', () => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
expect(response.body.devices).to.be.an('array'); expect(response.body.devices).to.be.an('array');
const deviceStatus = response.body.devices.find(d => d.id === device.id); const deviceStatus = response.body.devices.find(d => d.id === String(device.id));
expect(deviceStatus).to.exist; expect(deviceStatus).to.exist;
expect(deviceStatus.status).to.equal('online'); expect(deviceStatus.status).to.equal('online');
expect(deviceStatus.metrics).to.exist; expect(deviceStatus.metrics).to.exist;
@@ -288,8 +288,8 @@ describe('Healthcheck Routes', () => {
.set('Authorization', `Bearer ${token}`); .set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
const deviceStatus = response.body.devices.find(d => d.id === device.id); const deviceStatus = response.body.devices.find(d => d.id === String(device.id));
expect(deviceStatus.uptime_hours).to.be.a('number'); expect(deviceStatus.uptime).to.be.a('number');
}); });
}); });
@@ -504,9 +504,9 @@ describe('Healthcheck Routes', () => {
const token = generateTestToken(admin, tenant); const token = generateTestToken(admin, tenant);
// Generate some database activity // Generate some database activity
await createTestDevice({ tenant_id: tenant.id }); const device = await createTestDevice({ tenant_id: tenant.id });
await models.DroneDetection.create({ await models.DroneDetection.create({
device_id: 123, device_id: device.id,
tenant_id: tenant.id, tenant_id: tenant.id,
geo_lat: 59.3293, geo_lat: 59.3293,
geo_lon: 18.0686, geo_lon: 18.0686,
@@ -566,7 +566,7 @@ describe('Healthcheck Routes', () => {
.set('Authorization', `Bearer ${token}`); .set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
const deviceStatus = response.body.devices.find(d => d.id === device.id); const deviceStatus = response.body.devices.find(d => d.id === String(device.id));
expect(deviceStatus.status).to.be.oneOf(['offline', 'stale', 'unknown']); expect(deviceStatus.status).to.be.oneOf(['offline', 'stale', 'unknown']);
}); });