160 lines
4.3 KiB
JavaScript
160 lines
4.3 KiB
JavaScript
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();
|
|
|
|
// Initialize models reference (will be set when needed)
|
|
this.models = null;
|
|
}
|
|
|
|
// Set models reference for database logging
|
|
setModels(models) {
|
|
this.models = models;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
async 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);
|
|
}
|
|
}
|
|
|
|
// Store in database if models are available
|
|
if (this.models && this.models.AuditLog) {
|
|
try {
|
|
await this.models.AuditLog.create({
|
|
timestamp: new Date(),
|
|
level: level.toUpperCase(),
|
|
action: metadata.action || 'unknown',
|
|
message,
|
|
user_id: metadata.userId || null,
|
|
username: metadata.username || null,
|
|
tenant_id: metadata.tenantId || null,
|
|
tenant_slug: metadata.tenantSlug || null,
|
|
ip_address: metadata.ip || null,
|
|
user_agent: metadata.userAgent || null,
|
|
path: metadata.path || null,
|
|
metadata: metadata,
|
|
success: this.determineSuccess(level, metadata)
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to store audit log in database:', error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
determineSuccess(level, metadata) {
|
|
// Determine if the action was successful based on level and metadata
|
|
if (metadata.hasOwnProperty('success')) {
|
|
return metadata.success;
|
|
}
|
|
|
|
// Assume success for info level, failure for error/critical
|
|
switch (level.toUpperCase()) {
|
|
case 'INFO':
|
|
return true;
|
|
case 'WARNING':
|
|
return null; // Neutral
|
|
case 'ERROR':
|
|
case 'CRITICAL':
|
|
return false;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
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,
|
|
securityLogger
|
|
}; |