diff --git a/docker-compose.yml b/docker-compose.yml index 4df908d..db753e9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,6 +71,7 @@ services: STORE_RAW_PAYLOAD: ${STORE_RAW_PAYLOAD:-false} RATE_LIMIT_WINDOW_MS: ${RATE_LIMIT_WINDOW_MS:-900000} RATE_LIMIT_MAX_REQUESTS: ${RATE_LIMIT_MAX_REQUESTS:-1000} + SECURITY_LOG_DIR: /app/logs ports: - "3002:3001" volumes: diff --git a/server/middleware/ip-restriction.js b/server/middleware/ip-restriction.js index ec643bc..78f411b 100644 --- a/server/middleware/ip-restriction.js +++ b/server/middleware/ip-restriction.js @@ -5,6 +5,7 @@ const { Tenant } = require('../models'); const MultiTenantAuth = require('./multi-tenant-auth'); +const securityLogger = require('./logger'); class IPRestrictionMiddleware { constructor() { @@ -220,7 +221,12 @@ class IPRestrictionMiddleware { if (!isAllowed) { // Log the access attempt for security auditing - console.log(`[SECURITY AUDIT] ${new Date().toISOString()} - IP ${clientIP} denied access to tenant ${tenantId} - User-Agent: ${req.headers['user-agent']}`); + securityLogger.logIPRestriction( + clientIP, + tenantId, + req.headers['user-agent'], + true // denied + ); return res.status(403).json({ success: false, diff --git a/server/middleware/logger.js b/server/middleware/logger.js new file mode 100644 index 0000000..34895fc --- /dev/null +++ b/server/middleware/logger.js @@ -0,0 +1,106 @@ +const fs = require('fs'); +const path = require('path'); + +class SecurityLogger { + constructor() { + // Default to logs directory, but allow override via environment + this.logDir = process.env.SECURITY_LOG_DIR || path.join(__dirname, '..', 'logs'); + this.logFile = path.join(this.logDir, 'security-audit.log'); + + // Ensure log directory exists + this.ensureLogDirectory(); + } + + ensureLogDirectory() { + try { + if (!fs.existsSync(this.logDir)) { + fs.mkdirSync(this.logDir, { recursive: true }); + } + } catch (error) { + console.error('Failed to create log directory:', error.message); + // Fallback to console logging only + this.logFile = null; + } + } + + logSecurityEvent(level, message, metadata = {}) { + const timestamp = new Date().toISOString(); + const logEntry = { + timestamp, + level: level.toUpperCase(), + message, + ...metadata + }; + + // Always log to console for immediate visibility + console.log(`[SECURITY AUDIT] ${timestamp} - ${message}`); + + // Also log to file if available + if (this.logFile) { + try { + const logLine = JSON.stringify(logEntry) + '\n'; + fs.appendFileSync(this.logFile, logLine); + } catch (error) { + console.error('Failed to write to security log file:', error.message); + } + } + } + + logIPRestriction(ip, tenant, userAgent, denied = true) { + const action = denied ? 'denied access to' : 'granted access to'; + this.logSecurityEvent('WARNING', `IP ${ip} ${action} tenant ${tenant}`, { + type: 'IP_RESTRICTION', + ip, + tenant, + userAgent: userAgent || 'unknown', + denied + }); + } + + logAuthFailure(reason, metadata = {}) { + this.logSecurityEvent('ERROR', `Authentication failure: ${reason}`, { + type: 'AUTH_FAILURE', + reason, + ...metadata + }); + } + + logSuspiciousActivity(activity, metadata = {}) { + this.logSecurityEvent('CRITICAL', `Suspicious activity detected: ${activity}`, { + type: 'SUSPICIOUS_ACTIVITY', + activity, + ...metadata + }); + } + + // Get recent security events for monitoring + getRecentEvents(count = 100) { + if (!this.logFile || !fs.existsSync(this.logFile)) { + return []; + } + + try { + const content = fs.readFileSync(this.logFile, 'utf8'); + const lines = content.trim().split('\n').filter(line => line); + + return lines + .slice(-count) + .map(line => { + try { + return JSON.parse(line); + } catch { + return null; + } + }) + .filter(Boolean); + } catch (error) { + console.error('Failed to read security log file:', error.message); + return []; + } + } +} + +// Singleton instance +const securityLogger = new SecurityLogger(); + +module.exports = securityLogger; \ No newline at end of file