Fix jwt-token
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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.'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
272
server/utils/i18n.js
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user