From 4a62aa89603d4f8e9cf27b175b9f673d2b27bf82 Mon Sep 17 00:00:00 2001 From: Alexander Borg Date: Wed, 17 Sep 2025 18:40:40 +0200 Subject: [PATCH] Fix jwt-token --- create_stockholm_device.js | 65 +++++++++++ server/routes/detectors.js | 10 +- test_device_api.py | 217 +++++++++++++++++++++++++++++++++++++ 3 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 create_stockholm_device.js create mode 100644 test_device_api.py diff --git a/create_stockholm_device.js b/create_stockholm_device.js new file mode 100644 index 0000000..d5cbce0 --- /dev/null +++ b/create_stockholm_device.js @@ -0,0 +1,65 @@ +const { Device, Tenant } = require('./models'); + +async function createStockholmDevice() { + try { + // Find the uamils-ab tenant + const tenant = await Tenant.findOne({ where: { slug: 'uamils-ab' } }); + if (!tenant) { + console.log('❌ Tenant uamils-ab not found'); + return; + } + + // Check if device "1" already exists + const existingDevice = await Device.findOne({ where: { id: '1' } }); + if (existingDevice) { + console.log('✅ Stockholm Castle device already exists'); + console.log(` ID: ${existingDevice.id}`); + console.log(` Name: ${existingDevice.name}`); + console.log(` Approved: ${existingDevice.is_approved}`); + console.log(` Active: ${existingDevice.is_active}`); + console.log(` Tenant: ${existingDevice.tenant_id}`); + return; + } + + // Create Stockholm Castle device + const stockholmDevice = await Device.create({ + id: '1', + name: 'Stockholm Castle', + type: 'drone_detector', + location: 'Stockholm Castle, Sweden', + description: 'Drone detector at Stockholm Castle monitoring royal grounds', + is_approved: true, + is_active: true, + tenant_id: tenant.id, + coordinates: JSON.stringify({ + latitude: 59.3251, + longitude: 18.0719 + }), + config: JSON.stringify({ + detection_range: 25000, // 25km range + alert_threshold: 5000, // Alert when within 5km + frequency_bands: ['2.4GHz', '5.8GHz'], + sensitivity: 'high' + }) + }); + + console.log('✅ Stockholm Castle device created successfully'); + console.log(` ID: ${stockholmDevice.id}`); + console.log(` Name: ${stockholmDevice.name}`); + console.log(` Tenant: ${stockholmDevice.tenant_id}`); + console.log(` Approved: ${stockholmDevice.is_approved}`); + + } catch (error) { + console.error('❌ Error creating Stockholm device:', error.message); + } +} + +createStockholmDevice() + .then(() => { + console.log('✅ Stockholm device setup completed'); + process.exit(0); + }) + .catch(error => { + console.error('❌ Setup failed:', error); + process.exit(1); + }); \ No newline at end of file diff --git a/server/routes/detectors.js b/server/routes/detectors.js index 2e4ce42..0ed7a47 100644 --- a/server/routes/detectors.js +++ b/server/routes/detectors.js @@ -55,7 +55,10 @@ const detectorSchema = Joi.alternatives().try( type: Joi.string().valid('heartbeat').required(), key: Joi.string().required(), // Optional heartbeat fields - device_id: Joi.number().integer().optional(), + device_id: Joi.alternatives().try( + Joi.number().integer(), + 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(), @@ -65,7 +68,10 @@ const detectorSchema = Joi.alternatives().try( }), // Detection schema Joi.object({ - device_id: Joi.number().integer().required(), + device_id: Joi.alternatives().try( + Joi.number().integer(), + Joi.string() + ).required(), geo_lat: Joi.number().min(-90).max(90).required(), geo_lon: Joi.number().min(-180).max(180).required(), device_timestamp: Joi.number().integer().min(0).required(), diff --git a/test_device_api.py b/test_device_api.py new file mode 100644 index 0000000..44663af --- /dev/null +++ b/test_device_api.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +""" +Test script for device API authentication and detection submission +Simulates Stockholm Castle device sending drone detection data +""" + +import requests +import json +import sys +import os +from datetime import datetime, timezone + +# Configuration +API_BASE_URL = os.getenv('API_BASE_URL', 'http://localhost:3001/api') +TENANT_ID = os.getenv('TENANT_ID', 'uamils-ab') +DEVICE_ID = os.getenv('DEVICE_ID', '1') # Stockholm Castle device +USERNAME = os.getenv('TEST_USERNAME', 'admin') +PASSWORD = os.getenv('TEST_PASSWORD', 'admin123') + +# Headers for multi-tenant API +HEADERS = { + 'Content-Type': 'application/json', + 'x-tenant-id': TENANT_ID, + 'User-Agent': 'Stockholm-Castle-Device/1.0' +} + +class DeviceAPITester: + def __init__(self): + self.session = requests.Session() + self.session.headers.update(HEADERS) + self.jwt_token = None + + def authenticate(self): + """Authenticate with the backend and get JWT token""" + print(f"🔐 Authenticating as user: {USERNAME} for tenant: {TENANT_ID}") + + auth_data = { + 'username': USERNAME, + 'password': PASSWORD + } + + try: + response = self.session.post(f"{API_BASE_URL}/auth/local", json=auth_data) + + if response.status_code == 200: + data = response.json() + if data.get('success') and data.get('token'): + self.jwt_token = data['token'] + self.session.headers['Authorization'] = f'Bearer {self.jwt_token}' + print(f"✅ Authentication successful") + print(f" User: {data.get('user', {}).get('username', 'N/A')}") + print(f" Role: {data.get('user', {}).get('role', 'N/A')}") + print(f" Tenant: {data.get('user', {}).get('tenant_id', 'N/A')}") + return True + else: + print(f"❌ Authentication failed: Invalid response format") + print(f"Response: {response.text}") + return False + else: + print(f"❌ Authentication failed: {response.status_code}") + print(f"Response: {response.text}") + return False + + except requests.exceptions.RequestException as e: + print(f"❌ Authentication error: {e}") + return False + + def check_health(self): + """Check if API is healthy""" + try: + response = self.session.get(f"{API_BASE_URL}/health") + if response.status_code == 200: + print("✅ API health check passed") + return True + else: + print(f"❌ API health check failed: {response.status_code}") + return False + except requests.exceptions.RequestException as e: + print(f"❌ API health check error: {e}") + return False + + def send_detection(self, drone_type="Orlan", distance=1000, confidence=0.85): + """Send a drone detection to the API""" + detection_data = { + 'device_id': DEVICE_ID, + 'drone_type': drone_type, + 'confidence': confidence, + 'distance': distance, + 'signal_strength': -45.2, + 'frequency': 2400, + 'coordinates': { + 'latitude': 59.3251, # Stockholm Castle + 'longitude': 18.0719 + }, + 'raw_data': { + 'rssi': -45.2, + 'snr': 12.5, + 'frequency_mhz': 2400, + 'bandwidth': '20MHz', + 'detected_at': datetime.now(timezone.utc).isoformat() + }, + 'metadata': { + 'device_location': 'Stockholm Castle', + 'weather': 'Clear', + 'temperature': 15.2 + } + } + + print(f"📡 Sending detection: {drone_type} at {distance}m distance") + + try: + response = self.session.post(f"{API_BASE_URL}/detections", json=detection_data) + + if response.status_code == 201: + data = response.json() + print(f"✅ Detection sent successfully") + print(f" Detection ID: {data.get('id', 'N/A')}") + print(f" Timestamp: {data.get('timestamp', 'N/A')}") + return True + else: + print(f"❌ Detection failed: {response.status_code}") + print(f"Response: {response.text}") + return False + + except requests.exceptions.RequestException as e: + print(f"❌ Detection error: {e}") + return False + + def send_heartbeat(self): + """Send a heartbeat to show device is online""" + heartbeat_data = { + 'device_id': DEVICE_ID, + 'status': 'online', + 'battery_level': 95, + 'signal_quality': 85, + 'last_detection': datetime.now(timezone.utc).isoformat(), + 'location': { + 'latitude': 59.3251, + 'longitude': 18.0719 + }, + 'system_info': { + 'firmware_version': '1.2.3', + 'uptime': 3600, + 'cpu_usage': 25.5, + 'memory_usage': 45.2 + } + } + + print(f"💓 Sending heartbeat for device {DEVICE_ID}") + + try: + response = self.session.post(f"{API_BASE_URL}/heartbeat", json=heartbeat_data) + + if response.status_code in [200, 201]: + print(f"✅ Heartbeat sent successfully") + return True + else: + print(f"❌ Heartbeat failed: {response.status_code}") + print(f"Response: {response.text}") + return False + + except requests.exceptions.RequestException as e: + print(f"❌ Heartbeat error: {e}") + return False + +def main(): + print("=" * 70) + print("🎯 STOCKHOLM CASTLE DEVICE API TEST") + print("=" * 70) + print(f"API URL: {API_BASE_URL}") + print(f"Tenant: {TENANT_ID}") + print(f"Device ID: {DEVICE_ID}") + print(f"Username: {USERNAME}") + print("=" * 70) + + tester = DeviceAPITester() + + # Step 1: Health check + if not tester.check_health(): + print("❌ API not healthy. Please check if server is running.") + sys.exit(1) + + # Step 2: Authenticate + if not tester.authenticate(): + print("❌ Authentication failed. Please check credentials and tenant.") + print("\n💡 Troubleshooting:") + print("1. Ensure backend is running on correct port") + print("2. Check username/password are correct") + print("3. Verify tenant 'uamils-ab' exists") + print("4. Check database seeding completed successfully") + sys.exit(1) + + # Step 3: Send heartbeat + print("\n" + "=" * 50) + print("Testing Device Heartbeat") + print("=" * 50) + tester.send_heartbeat() + + # Step 4: Send detection + print("\n" + "=" * 50) + print("Testing Drone Detection") + print("=" * 50) + tester.send_detection(drone_type="Orlan", distance=1500, confidence=0.92) + + # Step 5: Send another detection (closer) + print("\n" + "=" * 50) + print("Testing Critical Alert (Close Drone)") + print("=" * 50) + tester.send_detection(drone_type="Orlan", distance=500, confidence=0.98) + + print("\n" + "=" * 70) + print("✅ TEST COMPLETED") + print("=" * 70) + print("Check the web interface to see the detections and device status") + +if __name__ == "__main__": + main() \ No newline at end of file