Initial commit: StreamFlow IPTV platform
This commit is contained in:
commit
73a8ae9ffd
1240 changed files with 278451 additions and 0 deletions
531
backend/utils/securityAudit.js
Normal file
531
backend/utils/securityAudit.js
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
/**
|
||||
* Security Audit Logger
|
||||
* Tracks security-related events for compliance and forensics
|
||||
* Integrated with SIEM for centralized log aggregation
|
||||
*/
|
||||
|
||||
const logger = require('./logger');
|
||||
const { db } = require('../database/db');
|
||||
const logAggregator = require('./logAggregator');
|
||||
|
||||
class SecurityAuditLogger {
|
||||
/**
|
||||
* Log authentication events
|
||||
*/
|
||||
static async logAuthEvent(eventType, userId, details = {}) {
|
||||
const event = {
|
||||
event_type: eventType,
|
||||
user_id: userId,
|
||||
ip_address: details.ip || 'unknown',
|
||||
user_agent: details.userAgent || 'unknown',
|
||||
success: details.success !== false,
|
||||
failure_reason: details.failureReason || null,
|
||||
metadata: JSON.stringify(details.metadata || {}),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`INSERT INTO security_audit_log (event_type, user_id, ip_address, user_agent, success, failure_reason, metadata, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[event.event_type, event.user_id, event.ip_address, event.user_agent, event.success ? 1 : 0, event.failure_reason, event.metadata, event.timestamp],
|
||||
(err) => err ? reject(err) : resolve()
|
||||
);
|
||||
});
|
||||
|
||||
logger.info(`[SECURITY] ${eventType}: user=${userId}, ip=${event.ip_address}, success=${event.success}`);
|
||||
|
||||
// Aggregate to SIEM
|
||||
const level = event.success ? 'info' : 'warn';
|
||||
const source = eventType.startsWith('LOGIN') || eventType.includes('PASSWORD') ? 'authentication' : 'security_audit';
|
||||
logAggregator.aggregate(source, level, 'authentication', `${eventType}: ${event.success ? 'success' : 'failure'}`, {
|
||||
userId: event.user_id,
|
||||
ip: event.ip_address,
|
||||
userAgent: event.user_agent,
|
||||
failureReason: event.failure_reason,
|
||||
metadata: details.metadata || {}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to log security event:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log login attempt
|
||||
*/
|
||||
static async logLoginAttempt(username, success, details = {}) {
|
||||
return this.logAuthEvent('LOGIN_ATTEMPT', null, {
|
||||
success,
|
||||
failureReason: success ? null : (details.reason || 'Invalid credentials'),
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: { username }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log successful login
|
||||
*/
|
||||
static async logLoginSuccess(userId, details = {}) {
|
||||
return this.logAuthEvent('LOGIN_SUCCESS', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: { twoFactorUsed: details.twoFactorUsed || false }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log failed login
|
||||
*/
|
||||
static async logLoginFailure(username, reason, details = {}) {
|
||||
return this.logAuthEvent('LOGIN_FAILURE', null, {
|
||||
success: false,
|
||||
failureReason: reason,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: { username }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log account lockout
|
||||
*/
|
||||
static async logAccountLockout(userId, details = {}) {
|
||||
return this.logAuthEvent('ACCOUNT_LOCKOUT', userId, {
|
||||
success: false,
|
||||
failureReason: 'Too many failed login attempts',
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: { failedAttempts: details.failedAttempts }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log password change
|
||||
*/
|
||||
static async logPasswordChange(userId, details = {}) {
|
||||
return this.logAuthEvent('PASSWORD_CHANGE', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: { forced: details.forced || false, expired: details.expired || false }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log 2FA events
|
||||
*/
|
||||
static async log2FAEvent(eventType, userId, success, details = {}) {
|
||||
return this.logAuthEvent(eventType, userId, {
|
||||
success,
|
||||
failureReason: success ? null : (details.reason || 'Invalid code'),
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: details.metadata || {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log session events
|
||||
*/
|
||||
static async logSessionEvent(eventType, userId, details = {}) {
|
||||
return this.logAuthEvent(eventType, userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: { sessionId: details.sessionId }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log privilege escalation
|
||||
*/
|
||||
static async logPrivilegeEscalation(userId, details = {}) {
|
||||
return this.logAuthEvent('PRIVILEGE_ESCALATION', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
fromRole: details.fromRole,
|
||||
toRole: details.toRole,
|
||||
grantedBy: details.grantedBy
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent security events for a user
|
||||
*/
|
||||
static async getUserSecurityEvents(userId, limit = 50) {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT * FROM security_audit_log
|
||||
WHERE user_id = ?
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ?`,
|
||||
[userId, limit],
|
||||
(err, rows) => err ? reject(err) : resolve(rows)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get failed login attempts for an IP
|
||||
*/
|
||||
static async getFailedAttemptsForIP(ipAddress, timeWindowMinutes = 30) {
|
||||
const cutoffTime = new Date(Date.now() - timeWindowMinutes * 60 * 1000).toISOString();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
`SELECT COUNT(*) as count
|
||||
FROM security_audit_log
|
||||
WHERE event_type IN ('LOGIN_FAILURE', 'LOGIN_ATTEMPT')
|
||||
AND success = 0
|
||||
AND ip_address = ?
|
||||
AND timestamp > ?`,
|
||||
[ipAddress, cutoffTime],
|
||||
(err, row) => err ? reject(err) : resolve(row.count || 0)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get failed login attempts for a user
|
||||
*/
|
||||
static async getFailedAttemptsForUser(username, timeWindowMinutes = 30) {
|
||||
const cutoffTime = new Date(Date.now() - timeWindowMinutes * 60 * 1000).toISOString();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
`SELECT COUNT(*) as count
|
||||
FROM security_audit_log
|
||||
WHERE event_type IN ('LOGIN_FAILURE', 'LOGIN_ATTEMPT')
|
||||
AND success = 0
|
||||
AND metadata LIKE ?
|
||||
AND timestamp > ?`,
|
||||
[`%"username":"${username}"%`, cutoffTime],
|
||||
(err, row) => err ? reject(err) : resolve(row.count || 0)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear old audit logs (data retention)
|
||||
*/
|
||||
static async cleanupOldLogs(retentionDays = 90) {
|
||||
const cutoffDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
'DELETE FROM security_audit_log WHERE timestamp < ?',
|
||||
[cutoffDate],
|
||||
function(err) {
|
||||
if (err) reject(err);
|
||||
else {
|
||||
logger.info(`Cleaned up ${this.changes} old security audit logs`);
|
||||
resolve(this.changes);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CWE-778: Log token issuance (JWT, OAuth, etc.)
|
||||
* Includes relevant metadata such as client ID, IP address, device info
|
||||
*/
|
||||
static async logTokenIssuance(userId, tokenType, details = {}) {
|
||||
return this.logAuthEvent('TOKEN_ISSUED', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
tokenType: tokenType, // 'JWT', 'REFRESH', 'TEMP_2FA', 'OAUTH'
|
||||
clientId: details.clientId,
|
||||
deviceInfo: details.deviceInfo || this.extractDeviceInfo(details.userAgent),
|
||||
expiresIn: details.expiresIn,
|
||||
purpose: details.purpose, // 'login', '2fa', 'registration', 'password_reset'
|
||||
scope: details.scope,
|
||||
grantType: details.grantType
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CWE-778: Log token refresh
|
||||
*/
|
||||
static async logTokenRefresh(userId, details = {}) {
|
||||
return this.logAuthEvent('TOKEN_REFRESHED', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
tokenType: details.tokenType || 'JWT',
|
||||
clientId: details.clientId,
|
||||
deviceInfo: details.deviceInfo || this.extractDeviceInfo(details.userAgent),
|
||||
oldTokenExpiry: details.oldTokenExpiry,
|
||||
newTokenExpiry: details.newTokenExpiry
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CWE-778: Log token revocation
|
||||
*/
|
||||
static async logTokenRevocation(userId, reason, details = {}) {
|
||||
return this.logAuthEvent('TOKEN_REVOKED', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
reason: reason, // 'logout', 'password_change', 'security_breach', 'admin_action'
|
||||
tokenType: details.tokenType || 'JWT',
|
||||
sessionId: details.sessionId,
|
||||
revokedBy: details.revokedBy // user_id of admin who revoked it
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CWE-778: Log privilege changes
|
||||
* Any activities where user's privilege level changes
|
||||
*/
|
||||
static async logPrivilegeChange(userId, action, details = {}) {
|
||||
return this.logAuthEvent('PRIVILEGE_CHANGE', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
action: action, // 'role_assigned', 'role_removed', 'permission_granted', 'permission_revoked'
|
||||
previousRole: details.previousRole,
|
||||
newRole: details.newRole,
|
||||
previousPermissions: details.previousPermissions,
|
||||
newPermissions: details.newPermissions,
|
||||
changedBy: details.changedBy, // user_id of admin who made the change
|
||||
changedByUsername: details.changedByUsername,
|
||||
reason: details.reason,
|
||||
affectedUser: details.affectedUser // username of the user being changed
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CWE-778: Log permission grant
|
||||
*/
|
||||
static async logPermissionGrant(userId, permission, details = {}) {
|
||||
return this.logAuthEvent('PERMISSION_GRANTED', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
permission: permission,
|
||||
grantedBy: details.grantedBy,
|
||||
grantedByUsername: details.grantedByUsername,
|
||||
scope: details.scope,
|
||||
expiresAt: details.expiresAt
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CWE-778: Log permission revocation
|
||||
*/
|
||||
static async logPermissionRevocation(userId, permission, details = {}) {
|
||||
return this.logAuthEvent('PERMISSION_REVOKED', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
permission: permission,
|
||||
revokedBy: details.revokedBy,
|
||||
revokedByUsername: details.revokedByUsername,
|
||||
reason: details.reason
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CWE-778: Log user activation/deactivation
|
||||
*/
|
||||
static async logAccountStatusChange(userId, newStatus, details = {}) {
|
||||
return this.logAuthEvent('ACCOUNT_STATUS_CHANGED', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
previousStatus: details.previousStatus,
|
||||
newStatus: newStatus, // 'active', 'inactive', 'suspended', 'locked'
|
||||
changedBy: details.changedBy,
|
||||
changedByUsername: details.changedByUsername,
|
||||
reason: details.reason
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CWE-778: Log administrative activities
|
||||
* For admin actions like user creation, deletion, unlock, force logout
|
||||
*/
|
||||
static async logAdminActivity(adminId, action, details = {}) {
|
||||
return this.logAuthEvent('ADMIN_ACTIVITY', adminId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
action: action, // 'user_created', 'user_deleted', 'account_unlocked', 'password_reset', 'force_logout'
|
||||
targetUserId: details.targetUserId,
|
||||
targetUsername: details.targetUsername,
|
||||
adminUsername: details.adminUsername,
|
||||
changes: details.changes,
|
||||
reason: details.reason,
|
||||
deviceInfo: details.deviceInfo || this.extractDeviceInfo(details.userAgent)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CWE-778: Log sensitive data access
|
||||
* For accessing user lists, settings, VPN configs, backups, etc.
|
||||
*/
|
||||
static async logSensitiveDataAccess(userId, dataType, details = {}) {
|
||||
return this.logAuthEvent('SENSITIVE_DATA_ACCESS', userId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
dataType: dataType, // 'user_list', 'user_details', 'settings', 'vpn_configs', 'backups', 'audit_logs'
|
||||
accessMethod: details.accessMethod || 'view', // 'view', 'export', 'download'
|
||||
recordCount: details.recordCount,
|
||||
filters: details.filters,
|
||||
scope: details.scope, // 'own', 'all', 'specific'
|
||||
deviceInfo: details.deviceInfo || this.extractDeviceInfo(details.userAgent)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract device info from user agent
|
||||
*/
|
||||
static extractDeviceInfo(userAgent = '') {
|
||||
if (!userAgent) return { deviceType: 'unknown', os: 'unknown', browser: 'unknown' };
|
||||
|
||||
const ua = userAgent.toLowerCase();
|
||||
|
||||
// Device type
|
||||
let deviceType = 'desktop';
|
||||
if (/bot|crawler|spider/.test(ua)) deviceType = 'bot';
|
||||
else if (/mobile|android|iphone|ipod/.test(ua)) deviceType = 'mobile';
|
||||
else if (/tablet|ipad/.test(ua)) deviceType = 'tablet';
|
||||
|
||||
// Operating System
|
||||
let os = 'unknown';
|
||||
if (/windows/.test(ua)) os = 'Windows';
|
||||
else if (/mac os|macos/.test(ua)) os = 'macOS';
|
||||
else if (/linux/.test(ua)) os = 'Linux';
|
||||
else if (/android/.test(ua)) os = 'Android';
|
||||
else if (/ios|iphone|ipad/.test(ua)) os = 'iOS';
|
||||
|
||||
// Browser
|
||||
let browser = 'unknown';
|
||||
if (/firefox/.test(ua)) browser = 'Firefox';
|
||||
else if (/chrome/.test(ua) && !/edge|edg/.test(ua)) browser = 'Chrome';
|
||||
else if (/safari/.test(ua) && !/chrome/.test(ua)) browser = 'Safari';
|
||||
else if (/edge|edg/.test(ua)) browser = 'Edge';
|
||||
else if (/opera|opr/.test(ua)) browser = 'Opera';
|
||||
|
||||
return { deviceType, os, browser };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comprehensive audit statistics
|
||||
*/
|
||||
static async getAuditStatistics(timeRangeDays = 30) {
|
||||
const cutoffDate = new Date(Date.now() - timeRangeDays * 24 * 60 * 60 * 1000).toISOString();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT
|
||||
event_type,
|
||||
success,
|
||||
COUNT(*) as count,
|
||||
COUNT(DISTINCT user_id) as unique_users,
|
||||
COUNT(DISTINCT ip_address) as unique_ips
|
||||
FROM security_audit_log
|
||||
WHERE timestamp > ?
|
||||
GROUP BY event_type, success
|
||||
ORDER BY count DESC`,
|
||||
[cutoffDate],
|
||||
(err, rows) => err ? reject(err) : resolve(rows)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log system events (startup, shutdown, cleanup, etc.)
|
||||
*/
|
||||
static async logSystemEvent(eventType, success, details = {}) {
|
||||
return this.logAuthEvent(`SYSTEM_${eventType.toUpperCase()}`, null, {
|
||||
success,
|
||||
failureReason: success ? null : (details.error || 'System event failed'),
|
||||
metadata: details
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log security incidents (tampering, breaches, etc.)
|
||||
*/
|
||||
static async logSecurityIncident(incidentType, details = {}) {
|
||||
const event = {
|
||||
event_type: `SECURITY_INCIDENT_${incidentType.toUpperCase()}`,
|
||||
user_id: details.userId || null,
|
||||
ip_address: details.ip || 'system',
|
||||
user_agent: details.userAgent || 'system',
|
||||
success: false,
|
||||
failure_reason: `Security incident: ${incidentType}`,
|
||||
metadata: JSON.stringify(details),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`INSERT INTO security_audit_log (event_type, user_id, ip_address, user_agent, success, failure_reason, metadata, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[event.event_type, event.user_id, event.ip_address, event.user_agent, 0, event.failure_reason, event.metadata, event.timestamp],
|
||||
(err) => err ? reject(err) : resolve()
|
||||
);
|
||||
});
|
||||
|
||||
logger.error(`[SECURITY INCIDENT] ${incidentType}: ${JSON.stringify(details)}`);
|
||||
|
||||
// Aggregate to SIEM with CRITICAL level
|
||||
logAggregator.aggregate('security_audit', 'critical', 'security_incident', `Security incident: ${incidentType}`, {
|
||||
incidentType,
|
||||
...details
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to log security incident:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log admin activities (user management, config changes, etc.)
|
||||
*/
|
||||
static async logAdminActivity(adminId, action, details = {}) {
|
||||
return this.logAuthEvent(`ADMIN_${action.toUpperCase()}`, adminId, {
|
||||
success: true,
|
||||
ip: details.ip,
|
||||
userAgent: details.userAgent,
|
||||
metadata: {
|
||||
action,
|
||||
target: details.target || details.targetUserId,
|
||||
changes: details.changes,
|
||||
...details
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SecurityAuditLogger;
|
||||
Loading…
Add table
Add a link
Reference in a new issue