Fix jwt-token

This commit is contained in:
2025-09-19 08:14:41 +02:00
parent f98fd04191
commit 3f1b50871a
5 changed files with 343 additions and 113 deletions

View File

@@ -1,4 +1,5 @@
const jwt = require('jsonwebtoken');
const { createErrorResponse, getLanguageFromRequest } = require('../utils/i18n');
// Allow models to be injected for testing
let models = null;
@@ -17,27 +18,24 @@ async function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
if (!authHeader) {
return res.status(401).json({
success: false,
message: 'Access token required'
});
const errorResponse = createErrorResponse(req, 401, 'NO_TOKEN');
errorResponse.json.redirectToLogin = true;
return res.status(errorResponse.status).json(errorResponse.json);
}
// Check for proper Bearer token format
if (!authHeader.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
message: 'Invalid token format'
});
const errorResponse = createErrorResponse(req, 401, 'INVALID_TOKEN');
errorResponse.json.redirectToLogin = true;
return res.status(errorResponse.status).json(errorResponse.json);
}
const token = authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({
success: false,
message: 'Access token required'
});
const errorResponse = createErrorResponse(req, 401, 'NO_TOKEN');
errorResponse.json.redirectToLogin = true;
return res.status(errorResponse.status).json(errorResponse.json);
}
try {
@@ -65,23 +63,15 @@ async function authenticateToken(req, res, next) {
});
if (!user) {
return res.status(401).json({
success: false,
message: 'User account not found. Please contact support.',
error: 'USER_NOT_FOUND',
errorCode: 'USER_NOT_FOUND',
redirectToLogin: true
});
const errorResponse = createErrorResponse(req, 401, 'USER_NOT_FOUND');
errorResponse.json.redirectToLogin = true;
return res.status(errorResponse.status).json(errorResponse.json);
}
if (!user.is_active) {
return res.status(401).json({
success: false,
message: 'Your account has been deactivated. Please contact support.',
error: 'ACCOUNT_DEACTIVATED',
errorCode: 'ACCOUNT_INACTIVE',
redirectToLogin: true
});
const errorResponse = createErrorResponse(req, 401, 'ACCOUNT_DEACTIVATED');
errorResponse.json.redirectToLogin = true;
return res.status(errorResponse.status).json(errorResponse.json);
}
// Set user context with expected properties for compatibility
@@ -123,39 +113,27 @@ async function authenticateToken(req, res, next) {
// Handle specific JWT errors with detailed responses
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
success: false,
error: 'TOKEN_EXPIRED',
message: 'Token expired',
redirectToLogin: true
});
const errorResponse = createErrorResponse(req, 401, 'TOKEN_EXPIRED');
errorResponse.json.redirectToLogin = true;
return res.status(errorResponse.status).json(errorResponse.json);
}
if (error.name === 'JsonWebTokenError') {
return res.status(401).json({
success: false,
error: 'INVALID_TOKEN',
message: 'Invalid token',
redirectToLogin: true
});
const errorResponse = createErrorResponse(req, 401, 'INVALID_TOKEN');
errorResponse.json.redirectToLogin = true;
return res.status(errorResponse.status).json(errorResponse.json);
}
if (error.name === 'NotBeforeError') {
return res.status(401).json({
success: false,
error: 'TOKEN_NOT_ACTIVE',
message: 'Token not active',
redirectToLogin: true
});
const errorResponse = createErrorResponse(req, 401, 'TOKEN_NOT_ACTIVE');
errorResponse.json.redirectToLogin = true;
return res.status(errorResponse.status).json(errorResponse.json);
}
// Generic authentication error
return res.status(401).json({
success: false,
error: 'AUTHENTICATION_FAILED',
message: 'Authentication failed',
redirectToLogin: true
});
const errorResponse = createErrorResponse(req, 401, 'AUTHENTICATION_FAILED');
errorResponse.json.redirectToLogin = true;
return res.status(errorResponse.status).json(errorResponse.json);
}
}

View File

@@ -9,16 +9,15 @@ const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs'); // Fixed: use bcryptjs instead of bcrypt
const { Op } = require('sequelize'); // Add Sequelize operators
const { Tenant, User, ManagementUser } = require('../models');
const { createErrorResponse, getSystemMessage, getLanguageFromRequest } = require('../utils/i18n');
// Management-specific authentication middleware - NO shared auth with tenants
const requireManagementAuth = (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({
success: false,
message: 'Management authentication required'
});
const errorResponse = createErrorResponse(req, 401, 'AUTHENTICATION_REQUIRED');
return res.status(errorResponse.status).json(errorResponse.json);
}
try {
@@ -28,10 +27,8 @@ const requireManagementAuth = (req, res, next) => {
// Verify this is a management token with proper role
if (!decoded.isManagement || !['super_admin', 'platform_admin'].includes(decoded.role)) {
return res.status(403).json({
success: false,
message: 'Insufficient management privileges'
});
const errorResponse = createErrorResponse(req, 403, 'INSUFFICIENT_PRIVILEGES');
return res.status(errorResponse.status).json(errorResponse.json);
}
req.managementUser = {
@@ -43,11 +40,8 @@ const requireManagementAuth = (req, res, next) => {
next();
} catch (error) {
return res.status(403).json({
success: false,
message: 'Invalid management token',
error: error.message
});
const errorResponse = createErrorResponse(req, 403, 'INVALID_TOKEN');
return res.status(errorResponse.status).json(errorResponse.json);
}
};
@@ -60,10 +54,8 @@ router.post('/auth/login', async (req, res) => {
const managementUser = await ManagementUser.findByCredentials(username, password);
if (!managementUser) {
return res.status(401).json({
success: false,
message: 'Invalid management credentials'
});
const errorResponse = createErrorResponse(req, 401, 'INVALID_MANAGEMENT_CREDENTIALS');
return res.status(errorResponse.status).json(errorResponse.json);
}
const MANAGEMENT_SECRET = process.env.MANAGEMENT_JWT_SECRET || 'mgmt-super-secret-change-in-production';
@@ -87,11 +79,9 @@ router.post('/auth/login', async (req, res) => {
}
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Management authentication error',
error: error.message
});
const errorResponse = createErrorResponse(req, 500, 'MANAGEMENT_AUTH_ERROR');
errorResponse.json.error = error.message;
return res.status(errorResponse.status).json(errorResponse.json);
}
});
@@ -718,10 +708,8 @@ router.post('/tenants', async (req, res) => {
// Enhanced validation for management portal
if (!tenantData.name || !tenantData.slug) {
return res.status(400).json({
success: false,
message: 'Name and slug are required'
});
const errorResponse = createErrorResponse(req, 400, 'NAME_SLUG_REQUIRED');
return res.status(errorResponse.status).json(errorResponse.json);
}
// Convert empty domain string to null to avoid unique constraint issues
@@ -732,20 +720,16 @@ router.post('/tenants', async (req, res) => {
// Check for unique slug
const existingTenant = await Tenant.findOne({ where: { slug: tenantData.slug } });
if (existingTenant) {
return res.status(409).json({
success: false,
message: 'Tenant slug already exists'
});
const errorResponse = createErrorResponse(req, 409, 'TENANT_SLUG_EXISTS');
return res.status(errorResponse.status).json(errorResponse.json);
}
// Check for unique domain if provided
if (tenantData.domain) {
const existingDomain = await Tenant.findOne({ where: { domain: tenantData.domain } });
if (existingDomain) {
return res.status(409).json({
success: false,
message: 'Domain already exists for another tenant'
});
const errorResponse = createErrorResponse(req, 409, 'DOMAIN_EXISTS');
return res.status(errorResponse.status).json(errorResponse.json);
}
}
@@ -757,15 +741,13 @@ router.post('/tenants', async (req, res) => {
res.status(201).json({
success: true,
data: tenant,
message: 'Tenant created successfully'
message: getSystemMessage(getLanguageFromRequest(req), 'TENANT_CREATED')
});
} catch (error) {
console.error('Management: Error creating tenant:', error);
res.status(500).json({
success: false,
message: 'Failed to create tenant'
});
const errorResponse = createErrorResponse(req, 500, 'TENANT_CREATION_FAILED');
return res.status(errorResponse.status).json(errorResponse.json);
}
});
@@ -783,10 +765,8 @@ router.get('/tenants/:id', async (req, res) => {
});
if (!tenant) {
return res.status(404).json({
success: false,
message: 'Tenant not found'
});
const errorResponse = createErrorResponse(req, 404, 'TENANT_NOT_FOUND');
return res.status(errorResponse.status).json(errorResponse.json);
}
res.json({
@@ -796,10 +776,8 @@ router.get('/tenants/:id', async (req, res) => {
} catch (error) {
console.error('Management: Error fetching tenant:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch tenant'
});
const errorResponse = createErrorResponse(req, 500, 'FETCH_TENANT_FAILED');
return res.status(errorResponse.status).json(errorResponse.json);
}
});

View File

@@ -37,7 +37,7 @@ describe('Authentication Integration Tests', () => {
expect(response.body).to.deep.equal({
success: false,
message: 'Access token required'
message: 'No authentication token provided.'
});
});
@@ -49,7 +49,7 @@ describe('Authentication Integration Tests', () => {
expect(response.body).to.deep.equal({
success: false,
message: 'Invalid token format'
message: 'Invalid authentication token. Please log in again.'
});
});
@@ -68,7 +68,7 @@ describe('Authentication Integration Tests', () => {
expect(response.body).to.deep.equal({
success: false,
error: 'TOKEN_EXPIRED',
message: 'Token expired',
message: 'Your session has expired. Please log in again.',
redirectToLogin: true
});
});
@@ -88,7 +88,7 @@ describe('Authentication Integration Tests', () => {
expect(response.body).to.deep.equal({
success: false,
error: 'INVALID_TOKEN',
message: 'Invalid token',
message: 'Invalid authentication token. Please log in again.',
redirectToLogin: true
});
});
@@ -107,7 +107,7 @@ describe('Authentication Integration Tests', () => {
expect(response.body).to.deep.equal({
success: false,
message: 'User not found'
message: 'User account not found. Please contact support.'
});
});
@@ -128,7 +128,7 @@ describe('Authentication Integration Tests', () => {
expect(response.body).to.deep.equal({
success: false,
message: 'User account is inactive'
message: 'Your account has been deactivated. Please contact support.'
});
});
@@ -194,7 +194,7 @@ describe('Authentication Integration Tests', () => {
expect(response.body).to.deep.equal({
success: false,
message: 'Insufficient permissions'
message: 'You do not have permission to perform this action.'
});
});
});

View File

@@ -36,7 +36,7 @@ describe('Authentication Middleware', () => {
expect(res.statusCode).to.equal(401);
expect(res.data).to.deep.equal({
success: false,
message: 'Access token required'
message: 'No authentication token provided.'
});
expect(next.errors).to.have.length(0);
});
@@ -53,7 +53,7 @@ describe('Authentication Middleware', () => {
expect(res.statusCode).to.equal(401);
expect(res.data).to.deep.equal({
success: false,
message: 'Invalid token format'
message: 'Invalid authentication token. Please log in again.'
});
});
@@ -68,7 +68,7 @@ describe('Authentication Middleware', () => {
expect(res.statusCode).to.equal(401);
expect(res.data.success).to.be.false;
expect(res.data.message).to.equal('Invalid token');
expect(res.data.message).to.equal('Invalid authentication token. Please log in again.');
});
it('should reject request with expired JWT token', async () => {
@@ -88,7 +88,7 @@ describe('Authentication Middleware', () => {
expect(res.statusCode).to.equal(401);
expect(res.data.success).to.be.false;
expect(res.data.message).to.equal('Token expired');
expect(res.data.message).to.equal('Your session has expired. Please log in again.');
});
it('should accept valid JWT token and set user data', async () => {
@@ -162,7 +162,7 @@ describe('Authentication Middleware', () => {
expect(res.statusCode).to.equal(401);
expect(res.data.success).to.be.false;
expect(res.data.message).to.equal('User not found');
expect(res.data.message).to.equal('User account not found. Please contact support.');
});
it('should reject inactive user', async () => {
@@ -186,7 +186,7 @@ describe('Authentication Middleware', () => {
expect(res.statusCode).to.equal(401);
expect(res.data.success).to.be.false;
expect(res.data.message).to.equal('User account is inactive');
expect(res.data.message).to.equal('Your account has been deactivated. Please contact support.');
});
it('should handle malformed JWT token', async () => {
@@ -326,7 +326,7 @@ describe('Authentication Middleware', () => {
expect(res.data).to.deep.equal({
success: false,
error: 'TOKEN_EXPIRED',
message: 'Token expired',
message: 'Your session has expired. Please log in again.',
redirectToLogin: true
});
});
@@ -344,7 +344,7 @@ describe('Authentication Middleware', () => {
expect(res.data).to.deep.equal({
success: false,
error: 'INVALID_TOKEN',
message: 'Invalid token',
message: 'Invalid authentication token. Please log in again.',
redirectToLogin: true
});
});
@@ -359,7 +359,9 @@ describe('Authentication Middleware', () => {
expect(res.statusCode).to.equal(401);
expect(res.data).to.deep.equal({
success: false,
message: 'Access token required'
error: 'NO_TOKEN',
message: 'No authentication token provided.',
redirectToLogin: true
});
});
});

272
server/utils/i18n.js Normal file
View File

@@ -0,0 +1,272 @@
/**
* Internationalization utility for backend error messages and system messages
*/
const SUPPORTED_LANGUAGES = ['en', 'sv'];
const DEFAULT_LANGUAGE = 'en';
// Error message translations
const ERROR_MESSAGES = {
en: {
// Authentication errors
TOKEN_EXPIRED: 'Your session has expired. Please log in again.',
INVALID_TOKEN: 'Invalid authentication token. Please log in again.',
TOKEN_NOT_ACTIVE: 'Authentication token is not yet active.',
USER_NOT_FOUND: 'User account not found. Please contact support.',
ACCOUNT_DEACTIVATED: 'Your account has been deactivated. Please contact support.',
AUTH_REQUIRED: 'Authentication is required to access this resource.',
NO_TOKEN: 'No authentication token provided.',
// Authorization errors
INSUFFICIENT_PERMISSIONS: 'Access denied. This action requires {requiredRoles} permissions, but you have {userRole} permissions.',
PERMISSION_DENIED: 'You do not have permission to perform this action.',
INSUFFICIENT_MANAGEMENT_PRIVILEGES: 'Insufficient management privileges to access this resource.',
// Management errors
AUTHENTICATION_REQUIRED: 'Management authentication required.',
INSUFFICIENT_PRIVILEGES: 'Insufficient management privileges.',
INVALID_MANAGEMENT_CREDENTIALS: 'Invalid management credentials.',
MANAGEMENT_AUTH_ERROR: 'Management authentication error.',
// Tenant management
TENANT_NOT_FOUND: 'Tenant not found.',
TENANT_CREATION_FAILED: 'Failed to create tenant.',
TENANT_UPDATE_FAILED: 'Failed to update tenant.',
TENANT_DELETE_FAILED: 'Failed to delete tenant.',
TENANT_SLUG_EXISTS: 'Tenant slug already exists.',
DOMAIN_EXISTS: 'Domain already exists for another tenant.',
CANNOT_DELETE_DEFAULT: 'Cannot delete default tenant.',
NAME_SLUG_REQUIRED: 'Name and slug are required.',
TENANT_CREATED: 'Tenant created successfully.',
TENANT_UPDATED: 'Tenant updated successfully.',
TENANT_DELETED: 'Tenant deleted successfully.',
FETCH_TENANTS_FAILED: 'Failed to fetch tenants.',
FETCH_TENANT_FAILED: 'Failed to fetch tenant.',
// System errors
SYSTEM_INFO_FAILED: 'Failed to fetch system information.',
DOCKER_ACCESS_ERROR: 'This could mean Docker is not running, no containers are active, or the monitoring system needs Docker access.',
MANAGEMENT_AUTH_REQUIRED: 'Management authentication is required to access this resource.',
INVALID_MANAGEMENT_TOKEN: 'Invalid management authentication token.',
// General errors
AUTHENTICATION_FAILED: 'Authentication failed. Please try again.',
ACCESS_DENIED: 'Access denied.',
VALIDATION_ERROR: 'Validation error occurred.',
INTERNAL_ERROR: 'An internal error occurred. Please try again later.'
},
sv: {
// Authentication errors
TOKEN_EXPIRED: 'Din session har löpt ut. Vänligen logga in igen.',
INVALID_TOKEN: 'Ogiltig autentiseringstoken. Vänligen logga in igen.',
TOKEN_NOT_ACTIVE: 'Autentiseringstoken är inte aktiv ännu.',
USER_NOT_FOUND: 'Användarkonto hittades inte. Vänligen kontakta support.',
ACCOUNT_DEACTIVATED: 'Ditt konto har inaktiverats. Vänligen kontakta support.',
AUTH_REQUIRED: 'Autentisering krävs för att komma åt denna resurs.',
NO_TOKEN: 'Ingen autentiseringstoken tillhandahållen.',
// Authorization errors
INSUFFICIENT_PERMISSIONS: 'Åtkomst nekad. Denna åtgärd kräver {requiredRoles} behörigheter, men du har {userRole} behörigheter.',
PERMISSION_DENIED: 'Du har inte behörighet att utföra denna åtgärd.',
INSUFFICIENT_MANAGEMENT_PRIVILEGES: 'Otillräckliga förvaltningsprivilegier för att komma åt denna resurs.',
// Management errors
MANAGEMENT_AUTH_REQUIRED: 'Förvaltningsautentisering krävs för att komma åt denna resurs.',
INVALID_MANAGEMENT_TOKEN: 'Ogiltig förvaltningsautentiseringstoken.',
AUTHENTICATION_REQUIRED: 'Förvaltningsautentisering krävs.',
INSUFFICIENT_PRIVILEGES: 'Otillräckliga förvaltningsprivilegier.',
INVALID_MANAGEMENT_CREDENTIALS: 'Ogiltiga förvaltningsuppgifter.',
MANAGEMENT_AUTH_ERROR: 'Förvaltningsautentiseringsfel.',
// Tenant management
TENANT_NOT_FOUND: 'Hyresgäst hittades inte.',
TENANT_CREATION_FAILED: 'Misslyckades att skapa hyresgäst.',
TENANT_UPDATE_FAILED: 'Misslyckades att uppdatera hyresgäst.',
TENANT_DELETE_FAILED: 'Misslyckades att ta bort hyresgäst.',
TENANT_SLUG_EXISTS: 'Hyresgästslug finns redan.',
DOMAIN_EXISTS: 'Domän finns redan för en annan hyresgäst.',
CANNOT_DELETE_DEFAULT: 'Kan inte ta bort standardhyresgäst.',
NAME_SLUG_REQUIRED: 'Namn och slug krävs.',
TENANT_CREATED: 'Hyresgäst skapad framgångsrikt.',
TENANT_UPDATED: 'Hyresgäst uppdaterad framgångsrikt.',
TENANT_DELETED: 'Hyresgäst borttagen framgångsrikt.',
FETCH_TENANTS_FAILED: 'Misslyckades att hämta hyresgäster.',
FETCH_TENANT_FAILED: 'Misslyckades att hämta hyresgäst.',
// System errors
SYSTEM_INFO_FAILED: 'Misslyckades att hämta systeminformation.',
DOCKER_ACCESS_ERROR: 'Detta kan betyda att Docker inte körs, inga behållare är aktiva eller att övervakningssystemet behöver Docker-åtkomst.',
// General errors
AUTHENTICATION_FAILED: 'Autentisering misslyckades. Vänligen försök igen.',
ACCESS_DENIED: 'Åtkomst nekad.',
VALIDATION_ERROR: 'Valideringsfel inträffade.',
INTERNAL_ERROR: 'Ett internt fel inträffade. Vänligen försök igen senare.'
}
};
// System message translations (for alerts, notifications, etc.)
const SYSTEM_MESSAGES = {
en: {
// Alert messages
CRITICAL_THREAT_DETECTED: 'CRITICAL THREAT: {droneType} DETECTED - IMMEDIATE RESPONSE REQUIRED',
HIGH_THREAT_DETECTED: 'HIGH THREAT: {droneType} detected at {distance}m',
MEDIUM_THREAT_DETECTED: 'MEDIUM THREAT: {droneType} detected in vicinity',
LOW_THREAT_DETECTED: 'LOW THREAT: {droneType} detected at distance',
MONITORING_DETECTED: 'MONITORING: {droneType} detected at long range',
MILITARY_THREAT_ENHANCED: 'MILITARY THREAT: {droneType} DETECTED - ENHANCED RESPONSE REQUIRED',
// Device messages
DEVICE_OFFLINE: 'Device {deviceName} is offline',
DEVICE_ONLINE: 'Device {deviceName} is back online',
HEARTBEAT_RECEIVED: 'Heartbeat received from {deviceName}',
// General system messages
OPERATION_SUCCESSFUL: 'Operation completed successfully',
OPERATION_FAILED: 'Operation failed',
DATA_SAVED: 'Data saved successfully',
DATA_DELETED: 'Data deleted successfully'
},
sv: {
// Alert messages
CRITICAL_THREAT_DETECTED: 'KRITISKT HOT: {droneType} UPPTÄCKT - OMEDELBAR RESPONS KRÄVS',
HIGH_THREAT_DETECTED: 'HÖGT HOT: {droneType} upptäckt på {distance}m',
MEDIUM_THREAT_DETECTED: 'MEDELHÖGT HOT: {droneType} upptäckt i närheten',
LOW_THREAT_DETECTED: 'LÅGT HOT: {droneType} upptäckt på avstånd',
MONITORING_DETECTED: 'ÖVERVAKNING: {droneType} upptäckt på långt avstånd',
MILITARY_THREAT_ENHANCED: 'MILITÄRT HOT: {droneType} UPPTÄCKT - FÖRSTÄRKT RESPONS KRÄVS',
// Device messages
DEVICE_OFFLINE: 'Enhet {deviceName} är offline',
DEVICE_ONLINE: 'Enhet {deviceName} är online igen',
HEARTBEAT_RECEIVED: 'Heartbeat mottagen från {deviceName}',
// General system messages
OPERATION_SUCCESSFUL: 'Operationen slutfördes framgångsrikt',
OPERATION_FAILED: 'Operationen misslyckades',
DATA_SAVED: 'Data sparad framgångsrikt',
DATA_DELETED: 'Data raderad framgångsrikt'
}
};
// Drone type translations
const DRONE_TYPES = {
en: {
0: 'Unknown Drone',
1: 'DJI Mavic',
2: 'Orlan',
3: 'Racing Drone',
4: 'DJI Phantom',
5: 'Zala Lancet',
6: 'Eleron',
7: 'Commercial Drone',
8: 'Professional Drone'
},
sv: {
0: 'Okänd Drönare',
1: 'DJI Mavic',
2: 'Orlan',
3: 'Racing Drönare',
4: 'DJI Phantom',
5: 'Zala Lancet',
6: 'Eleron',
7: 'Kommersiell Drönare',
8: 'Professionell Drönare'
}
};
/**
* Get user's preferred language from request headers
*/
function getLanguageFromRequest(req) {
// Check for explicit language header
const explicitLang = req.headers['accept-language-override'] || req.headers['x-language'];
if (explicitLang && SUPPORTED_LANGUAGES.includes(explicitLang)) {
return explicitLang;
}
// Parse Accept-Language header
const acceptLanguage = req.headers['accept-language'];
if (acceptLanguage) {
const languages = acceptLanguage
.split(',')
.map(lang => lang.split(';')[0].trim().toLowerCase())
.map(lang => lang.split('-')[0]); // Get just the language part (sv from sv-SE)
for (const lang of languages) {
if (SUPPORTED_LANGUAGES.includes(lang)) {
return lang;
}
}
}
return DEFAULT_LANGUAGE;
}
/**
* Get localized error message
*/
function getErrorMessage(messageKey, language = DEFAULT_LANGUAGE, params = {}) {
const lang = SUPPORTED_LANGUAGES.includes(language) ? language : DEFAULT_LANGUAGE;
let message = ERROR_MESSAGES[lang]?.[messageKey] || ERROR_MESSAGES[DEFAULT_LANGUAGE]?.[messageKey] || messageKey;
// Replace parameters in message
Object.keys(params).forEach(key => {
message = message.replace(new RegExp(`{${key}}`, 'g'), params[key]);
});
return message;
}
/**
* Get localized system message
*/
function getSystemMessage(messageKey, language = DEFAULT_LANGUAGE, params = {}) {
const lang = SUPPORTED_LANGUAGES.includes(language) ? language : DEFAULT_LANGUAGE;
let message = SYSTEM_MESSAGES[lang]?.[messageKey] || SYSTEM_MESSAGES[DEFAULT_LANGUAGE]?.[messageKey] || messageKey;
// Replace parameters in message
Object.keys(params).forEach(key => {
message = message.replace(new RegExp(`{${key}}`, 'g'), params[key]);
});
return message;
}
/**
* Get localized drone type name
*/
function getDroneTypeName(droneType, language = DEFAULT_LANGUAGE) {
const lang = SUPPORTED_LANGUAGES.includes(language) ? language : DEFAULT_LANGUAGE;
return DRONE_TYPES[lang]?.[droneType] || DRONE_TYPES[DEFAULT_LANGUAGE]?.[droneType] || 'Unknown';
}
/**
* Create localized error response
*/
function createErrorResponse(req, status, errorCode, params = {}) {
const language = getLanguageFromRequest(req);
const message = getErrorMessage(errorCode, language, params);
return {
status,
json: {
success: false,
message,
error: errorCode,
errorCode,
language,
...params
}
};
}
module.exports = {
getLanguageFromRequest,
getErrorMessage,
getSystemMessage,
getDroneTypeName,
createErrorResponse,
SUPPORTED_LANGUAGES,
DEFAULT_LANGUAGE
};