Fix jwt-token
This commit is contained in:
@@ -258,7 +258,23 @@ const translations = {
|
|||||||
changeLogo: 'Change Logo',
|
changeLogo: 'Change Logo',
|
||||||
confirmRemoveLogo: 'Are you sure you want to remove the current logo?',
|
confirmRemoveLogo: 'Are you sure you want to remove the current logo?',
|
||||||
logoRemoved: 'Logo removed successfully',
|
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: {
|
auth: {
|
||||||
login: 'Login',
|
login: 'Login',
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ const { Tenant, User } = require('../models');
|
|||||||
const { authenticateToken } = require('../middleware/auth');
|
const { authenticateToken } = require('../middleware/auth');
|
||||||
const { requirePermissions, requireAnyPermission, hasPermission } = require('../middleware/rbac');
|
const { requirePermissions, requireAnyPermission, hasPermission } = require('../middleware/rbac');
|
||||||
const MultiTenantAuth = require('../middleware/multi-tenant-auth');
|
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 multiAuth = new MultiTenantAuth();
|
||||||
|
const securityLogger = new SecurityLogger();
|
||||||
|
|
||||||
// Configure multer for logo uploads
|
// Configure multer for logo uploads
|
||||||
const storage = multer.diskStorage({
|
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
|
* POST /tenant/logo-upload
|
||||||
* Upload tenant logo (branding admin or higher)
|
* 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) => {
|
router.post('/logo-upload', authenticateToken, requirePermissions(['branding.edit']), upload.single('logo'), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
if (!req.file) {
|
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({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'No file uploaded'
|
message: 'No file uploaded'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine tenant from request
|
// Enhanced security validation
|
||||||
const tenantId = await multiAuth.determineTenant(req);
|
const validation = await validateTenantAccess(req, res, 'logo_upload');
|
||||||
if (!tenantId) {
|
if (!validation.success) {
|
||||||
return res.status(400).json({
|
// 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,
|
success: false,
|
||||||
message: 'Invalid tenant'
|
message: validation.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get tenant by slug
|
const { tenant } = validation;
|
||||||
const tenant = await Tenant.findOne({ where: { slug: tenantId } });
|
|
||||||
if (!tenant) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Tenant not found'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up old logo file if it exists
|
// Clean up old logo file if it exists
|
||||||
if (tenant.branding?.logo_url) {
|
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);
|
const oldFilePath = path.join(__dirname, '../uploads/logos', oldLogoPath);
|
||||||
if (fs.existsSync(oldFilePath)) {
|
if (fs.existsSync(oldFilePath)) {
|
||||||
fs.unlinkSync(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 });
|
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({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -173,6 +288,17 @@ router.post('/logo-upload', authenticateToken, requirePermissions(['branding.edi
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error uploading logo:', 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
|
// Clean up uploaded file on error
|
||||||
if (req.file && fs.existsSync(req.file.path)) {
|
if (req.file && fs.existsSync(req.file.path)) {
|
||||||
fs.unlinkSync(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) => {
|
router.delete('/logo', authenticateToken, requirePermissions(['branding.edit']), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
// Determine tenant from request
|
// Enhanced security validation
|
||||||
const tenantId = await multiAuth.determineTenant(req);
|
const validation = await validateTenantAccess(req, res, 'logo_removal');
|
||||||
if (!tenantId) {
|
if (!validation.success) {
|
||||||
return res.status(400).json({
|
return res.status(validation.status).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Unable to determine tenant from request'
|
message: validation.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get tenant
|
const { tenant } = validation;
|
||||||
const tenant = await Tenant.findOne({ where: { slug: tenantId } });
|
|
||||||
if (!tenant) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Tenant not found'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if tenant has a logo to remove
|
// Check if tenant has a logo to remove
|
||||||
if (!tenant.branding?.logo_url) {
|
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({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'No logo to remove'
|
message: 'No logo to remove'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldLogoUrl = tenant.branding.logo_url;
|
||||||
|
|
||||||
// Delete the physical logo file
|
// Delete the physical logo file
|
||||||
const logoPath = tenant.branding.logo_url.replace('/uploads/logos/', '');
|
const logoPath = tenant.branding.logo_url.replace('/uploads/logos/', '');
|
||||||
const filePath = path.join(__dirname, '../uploads/logos', logoPath);
|
const filePath = path.join(__dirname, '../uploads/logos', logoPath);
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
fs.unlinkSync(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
|
// Update tenant branding to remove logo
|
||||||
@@ -232,7 +370,19 @@ router.delete('/logo', authenticateToken, requirePermissions(['branding.edit']),
|
|||||||
|
|
||||||
await tenant.update({ branding: updatedBranding });
|
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({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -244,6 +394,17 @@ router.delete('/logo', authenticateToken, requirePermissions(['branding.edit']),
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error removing logo:', 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({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Failed to remove logo'
|
message: 'Failed to remove logo'
|
||||||
|
|||||||
Reference in New Issue
Block a user