From 1031f77ff6ced3ed44a871f9d2fba738120ebad5 Mon Sep 17 00:00:00 2001 From: Alexander Borg Date: Sat, 20 Sep 2025 06:46:48 +0200 Subject: [PATCH] Fix jwt-token --- client/src/utils/tempTranslations.js | 18 ++- server/routes/tenant.js | 219 +++++++++++++++++++++++---- 2 files changed, 207 insertions(+), 30 deletions(-) diff --git a/client/src/utils/tempTranslations.js b/client/src/utils/tempTranslations.js index d24db71..ca96b4e 100644 --- a/client/src/utils/tempTranslations.js +++ b/client/src/utils/tempTranslations.js @@ -258,7 +258,23 @@ const translations = { changeLogo: 'Change Logo', confirmRemoveLogo: 'Are you sure you want to remove the current logo?', logoRemoved: 'Logo removed successfully', - logoRemoveFailed: 'Failed to remove logo' + logoRemoveFailed: 'Failed to remove logo', + // Security and permissions + accessDenied: 'Access denied: Insufficient permissions', + invalidTenant: 'Invalid tenant', + tenantNotFound: 'Tenant not found', + userNotMemberOfTenant: 'Access denied: User not member of tenant', + authenticationRequired: 'Authentication required', + logoUploadError: 'Failed to upload logo', + logoRemovalError: 'Failed to remove logo', + logoUploadSuccess: 'Logo uploaded successfully', + logoRemovalSuccess: 'Logo removed successfully', + noFileUploaded: 'No file uploaded', + noLogoToRemove: 'No logo to remove', + internalServerError: 'Internal server error', + securityValidationFailed: 'Security validation failed', + unauthorizedAccess: 'Unauthorized access attempt', + tenantAccessDenied: 'Access denied for this tenant' }, auth: { login: 'Login', diff --git a/server/routes/tenant.js b/server/routes/tenant.js index 1ecb889..37caa06 100644 --- a/server/routes/tenant.js +++ b/server/routes/tenant.js @@ -12,9 +12,11 @@ const { Tenant, User } = require('../models'); const { authenticateToken } = require('../middleware/auth'); const { requirePermissions, requireAnyPermission, hasPermission } = require('../middleware/rbac'); const MultiTenantAuth = require('../middleware/multi-tenant-auth'); +const { SecurityLogger } = require('../middleware/logger'); -// Initialize multi-tenant auth +// Initialize multi-tenant auth and security logger const multiAuth = new MultiTenantAuth(); +const securityLogger = new SecurityLogger(); // Configure multer for logo uploads const storage = multer.diskStorage({ @@ -108,6 +110,93 @@ router.get('/info', authenticateToken, requirePermissions(['tenant.view']), asyn } }); +/** + * Validates user has access to tenant and logs the validation + */ +async function validateTenantAccess(req, res, action) { + try { + // Validate user is authenticated + if (!req.user || !req.user.id) { + securityLogger.logSecurityEvent('error', 'Tenant logo access attempt without valid user authentication', { + action, + ip: req.ip, + userAgent: req.get('User-Agent'), + path: req.path + }); + return { success: false, message: 'Authentication required', status: 401 }; + } + + // Determine tenant from request + const tenantId = await multiAuth.determineTenant(req); + if (!tenantId) { + securityLogger.logSecurityEvent('error', `Failed to determine tenant for ${action}`, { + action, + userId: req.user.id, + username: req.user.username, + ip: req.ip, + userAgent: req.get('User-Agent'), + path: req.path + }); + return { success: false, message: 'Invalid tenant', status: 400 }; + } + + // Get tenant by slug + const tenant = await Tenant.findOne({ where: { slug: tenantId } }); + if (!tenant) { + securityLogger.logSecurityEvent('error', `Tenant not found for ${action}`, { + action, + tenantSlug: tenantId, + userId: req.user.id, + username: req.user.username, + ip: req.ip, + userAgent: req.get('User-Agent'), + path: req.path + }); + return { success: false, message: 'Tenant not found', status: 404 }; + } + + // Validate user belongs to this tenant + if (req.user.tenant_id !== tenant.id) { + securityLogger.logSecurityEvent('warning', `User attempted ${action} on different tenant`, { + action, + userId: req.user.id, + username: req.user.username, + userTenantId: req.user.tenant_id, + requestedTenantId: tenant.id, + tenantSlug: tenantId, + ip: req.ip, + userAgent: req.get('User-Agent'), + path: req.path + }); + return { success: false, message: 'Access denied: User not member of tenant', status: 403 }; + } + + securityLogger.logSecurityEvent('info', `${action} access validated`, { + action, + userId: req.user.id, + username: req.user.username, + tenantId: tenant.id, + tenantSlug: tenantId, + ip: req.ip, + userAgent: req.get('User-Agent'), + path: req.path + }); + + return { success: true, tenant }; + } catch (error) { + securityLogger.logSecurityEvent('error', `Error validating tenant access for ${action}`, { + action, + error: error.message, + userId: req.user?.id, + username: req.user?.username, + ip: req.ip, + userAgent: req.get('User-Agent'), + path: req.path + }); + return { success: false, message: 'Internal server error', status: 500 }; + } +} + /** * POST /tenant/logo-upload * Upload tenant logo (branding admin or higher) @@ -115,29 +204,33 @@ router.get('/info', authenticateToken, requirePermissions(['tenant.view']), asyn router.post('/logo-upload', authenticateToken, requirePermissions(['branding.edit']), upload.single('logo'), async (req, res) => { try { if (!req.file) { + securityLogger.logSecurityEvent('warning', 'Logo upload attempted without file', { + action: 'logo_upload', + userId: req.user?.id, + username: req.user?.username, + ip: req.ip, + userAgent: req.get('User-Agent') + }); return res.status(400).json({ success: false, message: 'No file uploaded' }); } - // Determine tenant from request - const tenantId = await multiAuth.determineTenant(req); - if (!tenantId) { - return res.status(400).json({ + // Enhanced security validation + const validation = await validateTenantAccess(req, res, 'logo_upload'); + if (!validation.success) { + // Clean up uploaded file on validation failure + if (fs.existsSync(req.file.path)) { + fs.unlinkSync(req.file.path); + } + return res.status(validation.status).json({ success: false, - message: 'Invalid tenant' + message: validation.message }); } - // Get tenant by slug - const tenant = await Tenant.findOne({ where: { slug: tenantId } }); - if (!tenant) { - return res.status(404).json({ - success: false, - message: 'Tenant not found' - }); - } + const { tenant } = validation; // Clean up old logo file if it exists if (tenant.branding?.logo_url) { @@ -145,6 +238,14 @@ router.post('/logo-upload', authenticateToken, requirePermissions(['branding.edi const oldFilePath = path.join(__dirname, '../uploads/logos', oldLogoPath); if (fs.existsSync(oldFilePath)) { fs.unlinkSync(oldFilePath); + securityLogger.logSecurityEvent('info', 'Old logo file cleaned up', { + action: 'logo_upload', + oldLogoPath, + userId: req.user.id, + username: req.user.username, + tenantId: tenant.id, + tenantSlug: tenant.slug + }); } } @@ -159,7 +260,21 @@ router.post('/logo-upload', authenticateToken, requirePermissions(['branding.edi await tenant.update({ branding: updatedBranding }); - console.log(`✅ Tenant "${tenantId}" logo uploaded by user "${req.user.username}"`); + // Audit log successful upload + securityLogger.logSecurityEvent('info', 'Tenant logo uploaded successfully', { + action: 'logo_upload', + userId: req.user.id, + username: req.user.username, + tenantId: tenant.id, + tenantSlug: tenant.slug, + fileName: req.file.filename, + fileSize: req.file.size, + logoUrl, + ip: req.ip, + userAgent: req.get('User-Agent') + }); + + console.log(`✅ Tenant "${tenant.slug}" logo uploaded by user "${req.user.username}"`); res.json({ success: true, @@ -173,6 +288,17 @@ router.post('/logo-upload', authenticateToken, requirePermissions(['branding.edi } catch (error) { console.error('Error uploading logo:', error); + // Audit log the error + securityLogger.logSecurityEvent('error', 'Logo upload failed with error', { + action: 'logo_upload', + error: error.message, + userId: req.user?.id, + username: req.user?.username, + fileName: req.file?.filename, + ip: req.ip, + userAgent: req.get('User-Agent') + }); + // Clean up uploaded file on error if (req.file && fs.existsSync(req.file.path)) { fs.unlinkSync(req.file.path); @@ -191,37 +317,49 @@ router.post('/logo-upload', authenticateToken, requirePermissions(['branding.edi */ router.delete('/logo', authenticateToken, requirePermissions(['branding.edit']), async (req, res) => { try { - // Determine tenant from request - const tenantId = await multiAuth.determineTenant(req); - if (!tenantId) { - return res.status(400).json({ + // Enhanced security validation + const validation = await validateTenantAccess(req, res, 'logo_removal'); + if (!validation.success) { + return res.status(validation.status).json({ success: false, - message: 'Unable to determine tenant from request' + message: validation.message }); } - // Get tenant - const tenant = await Tenant.findOne({ where: { slug: tenantId } }); - if (!tenant) { - return res.status(404).json({ - success: false, - message: 'Tenant not found' - }); - } + const { tenant } = validation; // Check if tenant has a logo to remove if (!tenant.branding?.logo_url) { + securityLogger.logSecurityEvent('warning', 'Logo removal attempted when no logo exists', { + action: 'logo_removal', + userId: req.user.id, + username: req.user.username, + tenantId: tenant.id, + tenantSlug: tenant.slug, + ip: req.ip, + userAgent: req.get('User-Agent') + }); return res.status(400).json({ success: false, message: 'No logo to remove' }); } + const oldLogoUrl = tenant.branding.logo_url; + // Delete the physical logo file const logoPath = tenant.branding.logo_url.replace('/uploads/logos/', ''); const filePath = path.join(__dirname, '../uploads/logos', logoPath); if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); + securityLogger.logSecurityEvent('info', 'Logo file deleted from filesystem', { + action: 'logo_removal', + logoPath, + userId: req.user.id, + username: req.user.username, + tenantId: tenant.id, + tenantSlug: tenant.slug + }); } // Update tenant branding to remove logo @@ -232,7 +370,19 @@ router.delete('/logo', authenticateToken, requirePermissions(['branding.edit']), await tenant.update({ branding: updatedBranding }); - console.log(`✅ Tenant "${tenantId}" logo removed by user "${req.user.username}"`); + // Audit log successful removal + securityLogger.logSecurityEvent('info', 'Tenant logo removed successfully', { + action: 'logo_removal', + userId: req.user.id, + username: req.user.username, + tenantId: tenant.id, + tenantSlug: tenant.slug, + removedLogoUrl: oldLogoUrl, + ip: req.ip, + userAgent: req.get('User-Agent') + }); + + console.log(`✅ Tenant "${tenant.slug}" logo removed by user "${req.user.username}"`); res.json({ success: true, @@ -244,6 +394,17 @@ router.delete('/logo', authenticateToken, requirePermissions(['branding.edit']), } catch (error) { console.error('Error removing logo:', error); + + // Audit log the error + securityLogger.logSecurityEvent('error', 'Logo removal failed with error', { + action: 'logo_removal', + error: error.message, + userId: req.user?.id, + username: req.user?.username, + ip: req.ip, + userAgent: req.get('User-Agent') + }); + res.status(500).json({ success: false, message: 'Failed to remove logo'