diff --git a/src/graphql/security/advanced-audit-reporting.ts b/src/graphql/security/advanced-audit-reporting.ts new file mode 100644 index 0000000..ef67b1d --- /dev/null +++ b/src/graphql/security/advanced-audit-reporting.ts @@ -0,0 +1,726 @@ +/** + * Расширенная система отчетности по аудиту безопасности + * + * Предоставляет детальные отчеты, аналитику и визуализацию + * активности пользователей в системе для выявления паттернов и угроз + */ + +import { PrismaClient } from '@prisma/client' +import { SecurityLogger } from '../../lib/security-logger' +import { CommercialAccessType, ResourceType, SecurityAlert } from './types' + +/** + * Детальные метрики безопасности + */ +interface SecurityMetrics { + totalAccesses: number + uniqueUsers: number + topActions: Array<{ action: string; count: number }> + organizationBreakdown: Array<{ type: string; count: number }> + timeDistribution: Array<{ hour: number; count: number }> + suspiciousActivity: number + resolvedAlerts: number + activeAlerts: number +} + +/** + * Аналитика пользователя + */ +interface UserAnalytics { + userId: string + organizationType: string + organizationId: string + totalAccesses: number + uniqueResources: number + lastActivity: Date + riskScore: number + activities: Array<{ + action: CommercialAccessType + resourceType: ResourceType + count: number + avgPerHour: number + maxPerHour: number + timeRange: { start: Date; end: Date } + }> + anomalies: Array<{ + type: 'VOLUME_SPIKE' | 'UNUSUAL_TIME' | 'NEW_RESOURCE' | 'RAPID_SUCCESSION' + description: string + severity: 'LOW' | 'MEDIUM' | 'HIGH' + timestamp: Date + }> +} + +/** + * Отчет по организации + */ +interface OrganizationReport { + organizationId: string + organizationType: string + period: { start: Date; end: Date } + users: number + totalActivity: number + breakdown: { + viewing: number + modifying: number + exporting: number + } + compliance: { + dataAccess: 'COMPLIANT' | 'CONCERNING' | 'VIOLATION' + partnerships: 'VALID' | 'QUESTIONABLE' | 'INVALID' + timePatterns: 'NORMAL' | 'UNUSUAL' + } + alerts: { + generated: number + resolved: number + highPriority: number + } +} + +/** + * Тренды безопасности + */ +interface SecurityTrends { + period: { start: Date; end: Date } + dataPoints: Array<{ + timestamp: Date + totalAccesses: number + uniqueUsers: number + alerts: number + riskScore: number + }> + predictions: { + nextPeriodRisk: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' + expectedVolume: number + recommendedActions: string[] + } +} + +export class AdvancedAuditReporting { + /** + * Генерирует полный отчет по безопасности за период + */ + static async generateSecurityReport( + prisma: PrismaClient, + startDate: Date, + endDate: Date, + ): Promise { + try { + // Основные метрики + const totalAccesses = await prisma.auditLog.count({ + where: { + timestamp: { gte: startDate, lte: endDate }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + }) + + const uniqueUsers = await prisma.auditLog + .groupBy({ + by: ['userId'], + where: { + timestamp: { gte: startDate, lte: endDate }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + }) + .then((groups) => groups.length) + + // Топ действий + const topActionsRaw = await prisma.auditLog.groupBy({ + by: ['action'], + where: { + timestamp: { gte: startDate, lte: endDate }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + _count: { action: true }, + orderBy: { _count: { action: 'desc' } }, + take: 10, + }) + + const topActions = topActionsRaw.map((item) => ({ + action: item.action.replace('DATA_ACCESS:', ''), + count: item._count.action, + })) + + // Распределение по типам организаций + const organizationBreakdownRaw = await prisma.auditLog.groupBy({ + by: ['organizationType'], + where: { + timestamp: { gte: startDate, lte: endDate }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + _count: { organizationType: true }, + }) + + const organizationBreakdown = organizationBreakdownRaw.map((item) => ({ + type: item.organizationType || 'UNKNOWN', + count: item._count.organizationType, + })) + + // Распределение по времени (по часам) + const timeDistributionRaw = await prisma.$queryRaw>` + SELECT EXTRACT(HOUR FROM timestamp) as hour, COUNT(*) as count + FROM AuditLog + WHERE timestamp >= ${startDate} + AND timestamp <= ${endDate} + AND action LIKE 'DATA_ACCESS:%' + GROUP BY EXTRACT(HOUR FROM timestamp) + ORDER BY hour + ` + + const timeDistribution = timeDistributionRaw.map((item) => ({ + hour: Number(item.hour), + count: Number(item.count), + })) + + // Подозрительная активность + const suspiciousActivity = await prisma.auditLog.count({ + where: { + timestamp: { gte: startDate, lte: endDate }, + action: 'UNAUTHORIZED_ACCESS_ATTEMPT', + }, + }) + + // Алерты + const resolvedAlerts = await prisma.securityAlert.count({ + where: { + timestamp: { gte: startDate, lte: endDate }, + resolved: true, + }, + }) + + const activeAlerts = await prisma.securityAlert.count({ + where: { + timestamp: { gte: startDate, lte: endDate }, + resolved: false, + }, + }) + + return { + totalAccesses, + uniqueUsers, + topActions, + organizationBreakdown, + timeDistribution, + suspiciousActivity, + resolvedAlerts, + activeAlerts, + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'generateSecurityReport', + period: { startDate, endDate }, + }) + throw error + } + } + + /** + * Генерирует детальную аналитику пользователя + */ + static async generateUserAnalytics( + prisma: PrismaClient, + userId: string, + period: Date = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), + ): Promise { + try { + const user = await prisma.user.findUnique({ + where: { id: userId }, + include: { organization: true }, + }) + + if (!user) { + throw new Error(`User ${userId} not found`) + } + + const since = period + const now = new Date() + + // Основные метрики + const totalAccesses = await prisma.auditLog.count({ + where: { + userId, + timestamp: { gte: since }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + }) + + const uniqueResourcesRaw = await prisma.auditLog.groupBy({ + by: ['resourceId'], + where: { + userId, + timestamp: { gte: since }, + action: { startsWith: 'DATA_ACCESS:' }, + resourceId: { not: null }, + }, + }) + + const uniqueResources = uniqueResourcesRaw.length + + const lastActivityRaw = await prisma.auditLog.findFirst({ + where: { + userId, + timestamp: { gte: since }, + }, + orderBy: { timestamp: 'desc' }, + }) + + const lastActivity = lastActivityRaw?.timestamp || new Date(0) + + // Детальная активность по действиям + const activitiesRaw = await prisma.auditLog.groupBy({ + by: ['action', 'resourceType'], + where: { + userId, + timestamp: { gte: since }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + _count: { action: true }, + _min: { timestamp: true }, + _max: { timestamp: true }, + }) + + const activities = activitiesRaw.map((item) => { + const hoursSinceStart = Math.max( + 1, + (now.getTime() - (item._min.timestamp?.getTime() || now.getTime())) / (1000 * 60 * 60), + ) + + return { + action: item.action.replace('DATA_ACCESS:', '') as CommercialAccessType, + resourceType: item.resourceType as ResourceType, + count: item._count.action, + avgPerHour: item._count.action / hoursSinceStart, + maxPerHour: await this.getMaxHourlyActivity(prisma, userId, item.action, since), + timeRange: { + start: item._min.timestamp || since, + end: item._max.timestamp || now, + }, + } + }) + + // Вычисление аномалий + const anomalies = await this.detectUserAnomalies(prisma, userId, since, activities) + + // Расчет risk score + const riskScore = this.calculateRiskScore(activities, anomalies, totalAccesses) + + return { + userId, + organizationType: user.organization?.type || 'UNKNOWN', + organizationId: user.organizationId || '', + totalAccesses, + uniqueResources, + lastActivity, + riskScore, + activities, + anomalies, + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'generateUserAnalytics', + userId, + period, + }) + throw error + } + } + + /** + * Генерирует отчет по организации + */ + static async generateOrganizationReport( + prisma: PrismaClient, + organizationId: string, + startDate: Date, + endDate: Date, + ): Promise { + try { + const organization = await prisma.organization.findUnique({ + where: { id: organizationId }, + include: { users: true }, + }) + + if (!organization) { + throw new Error(`Organization ${organizationId} not found`) + } + + const userIds = organization.users.map((user) => user.id) + + // Основные метрики + const totalActivity = await prisma.auditLog.count({ + where: { + userId: { in: userIds }, + timestamp: { gte: startDate, lte: endDate }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + }) + + // Классификация активности + const viewingActions = await prisma.auditLog.count({ + where: { + userId: { in: userIds }, + timestamp: { gte: startDate, lte: endDate }, + action: { in: ['DATA_ACCESS:VIEW_PRICE', 'DATA_ACCESS:VIEW_RECIPE', 'DATA_ACCESS:VIEW_CONTACTS'] }, + }, + }) + + const modifyingActions = await prisma.auditLog.count({ + where: { + userId: { in: userIds }, + timestamp: { gte: startDate, lte: endDate }, + action: { contains: 'UPDATE' }, + }, + }) + + const exportingActions = await prisma.auditLog.count({ + where: { + userId: { in: userIds }, + timestamp: { gte: startDate, lte: endDate }, + action: { contains: 'EXPORT' }, + }, + }) + + // Анализ соответствия + const compliance = await this.analyzeOrganizationCompliance( + prisma, + organizationId, + userIds, + startDate, + endDate, + ) + + // Алерты + const generatedAlerts = await prisma.securityAlert.count({ + where: { + userId: { in: userIds }, + timestamp: { gte: startDate, lte: endDate }, + }, + }) + + const resolvedAlerts = await prisma.securityAlert.count({ + where: { + userId: { in: userIds }, + timestamp: { gte: startDate, lte: endDate }, + resolved: true, + }, + }) + + const highPriorityAlerts = await prisma.securityAlert.count({ + where: { + userId: { in: userIds }, + timestamp: { gte: startDate, lte: endDate }, + severity: { in: ['HIGH', 'CRITICAL'] }, + }, + }) + + return { + organizationId, + organizationType: organization.type, + period: { start: startDate, end: endDate }, + users: userIds.length, + totalActivity, + breakdown: { + viewing: viewingActions, + modifying: modifyingActions, + exporting: exportingActions, + }, + compliance, + alerts: { + generated: generatedAlerts, + resolved: resolvedAlerts, + highPriority: highPriorityAlerts, + }, + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'generateOrganizationReport', + organizationId, + period: { startDate, endDate }, + }) + throw error + } + } + + /** + * Анализирует тренды безопасности и делает прогнозы + */ + static async analyzeSecurityTrends( + prisma: PrismaClient, + days: number = 30, + ): Promise { + try { + const endDate = new Date() + const startDate = new Date(endDate.getTime() - days * 24 * 60 * 60 * 1000) + + // Получаем данные по дням + const dailyDataRaw = await prisma.$queryRaw< + Array<{ + date: Date + totalAccesses: bigint + uniqueUsers: bigint + alerts: bigint + }> + >` + SELECT + DATE(timestamp) as date, + COUNT(CASE WHEN action LIKE 'DATA_ACCESS:%' THEN 1 END) as totalAccesses, + COUNT(DISTINCT CASE WHEN action LIKE 'DATA_ACCESS:%' THEN userId END) as uniqueUsers, + COUNT(CASE WHEN action = 'UNAUTHORIZED_ACCESS_ATTEMPT' THEN 1 END) as alerts + FROM AuditLog + WHERE timestamp >= ${startDate} AND timestamp <= ${endDate} + GROUP BY DATE(timestamp) + ORDER BY date + ` + + const dataPoints = dailyDataRaw.map((item, index) => { + // Простой расчет risk score на основе активности и алертов + const totalAccesses = Number(item.totalAccesses) + const alerts = Number(item.alerts) + const riskScore = Math.min(100, (alerts * 20) + Math.max(0, totalAccesses - 1000) / 100) + + return { + timestamp: item.date, + totalAccesses, + uniqueUsers: Number(item.uniqueUsers), + alerts, + riskScore, + } + }) + + // Простое прогнозирование на основе трендов + const predictions = this.generateSecurityPredictions(dataPoints) + + return { + period: { start: startDate, end: endDate }, + dataPoints, + predictions, + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'analyzeSecurityTrends', + days, + }) + throw error + } + } + + /** + * Получает максимальную почасовую активность пользователя + */ + private static async getMaxHourlyActivity( + prisma: PrismaClient, + userId: string, + action: string, + since: Date, + ): Promise { + try { + const hourlyData = await prisma.$queryRaw>` + SELECT COUNT(*) as count + FROM AuditLog + WHERE userId = ${userId} + AND action = ${action} + AND timestamp >= ${since} + GROUP BY DATE(timestamp), EXTRACT(HOUR FROM timestamp) + ORDER BY count DESC + LIMIT 1 + ` + + return hourlyData.length > 0 ? Number(hourlyData[0].count) : 0 + } catch (error) { + return 0 + } + } + + /** + * Обнаруживает аномалии в поведении пользователя + */ + private static async detectUserAnomalies( + prisma: PrismaClient, + userId: string, + since: Date, + activities: UserAnalytics['activities'], + ): Promise { + const anomalies: UserAnalytics['anomalies'] = [] + + for (const activity of activities) { + // Аномалия объема - превышение нормальной активности в 3+ раза + if (activity.maxPerHour > activity.avgPerHour * 3 && activity.avgPerHour > 10) { + anomalies.push({ + type: 'VOLUME_SPIKE', + description: `Spike in ${activity.action} activity: ${activity.maxPerHour} per hour vs average ${activity.avgPerHour.toFixed(1)}`, + severity: activity.maxPerHour > activity.avgPerHour * 5 ? 'HIGH' : 'MEDIUM', + timestamp: activity.timeRange.end, + }) + } + + // Аномалия времени - активность в необычные часы (поздно ночью/рано утром) + const nightActivity = await prisma.auditLog.count({ + where: { + userId, + action: `DATA_ACCESS:${activity.action}`, + timestamp: { gte: since }, + }, + }) + + const totalActivity = activity.count + const nightRatio = nightActivity / totalActivity + + if (nightRatio > 0.3) { + // Более 30% активности ночью + anomalies.push({ + type: 'UNUSUAL_TIME', + description: `High night-time activity: ${(nightRatio * 100).toFixed(1)}% of ${activity.action} actions`, + severity: nightRatio > 0.5 ? 'MEDIUM' : 'LOW', + timestamp: activity.timeRange.end, + }) + } + } + + return anomalies + } + + /** + * Вычисляет оценку риска пользователя + */ + private static calculateRiskScore( + activities: UserAnalytics['activities'], + anomalies: UserAnalytics['anomalies'], + totalAccesses: number, + ): number { + let score = 0 + + // Базовый score от объема активности + score += Math.min(30, totalAccesses / 100) + + // Score от аномалий + anomalies.forEach((anomaly) => { + switch (anomaly.severity) { + case 'LOW': + score += 5 + break + case 'MEDIUM': + score += 15 + break + case 'HIGH': + score += 30 + break + } + }) + + // Score от разнообразия активности + const uniqueActions = activities.length + score += Math.min(20, uniqueActions * 3) + + return Math.min(100, Math.max(0, score)) + } + + /** + * Анализирует соответствие организации требованиям безопасности + */ + private static async analyzeOrganizationCompliance( + prisma: PrismaClient, + organizationId: string, + userIds: string[], + startDate: Date, + endDate: Date, + ): Promise { + // Анализ доступа к данным + const unauthorizedAttempts = await prisma.auditLog.count({ + where: { + userId: { in: userIds }, + timestamp: { gte: startDate, lte: endDate }, + action: 'UNAUTHORIZED_ACCESS_ATTEMPT', + }, + }) + + const dataAccess = unauthorizedAttempts === 0 ? 'COMPLIANT' : unauthorizedAttempts > 10 ? 'VIOLATION' : 'CONCERNING' + + // Анализ партнерств (упрощенный) + const partnerships = 'VALID' // TODO: реализовать анализ партнерств + + // Анализ временных паттернов + const nightActivity = await prisma.$queryRaw>` + SELECT COUNT(*) as count + FROM AuditLog + WHERE userId IN (${userIds.join(',')}) + AND timestamp >= ${startDate} + AND timestamp <= ${endDate} + AND EXTRACT(HOUR FROM timestamp) BETWEEN 0 AND 6 + AND action LIKE 'DATA_ACCESS:%' + ` + + const totalActivity = await prisma.auditLog.count({ + where: { + userId: { in: userIds }, + timestamp: { gte: startDate, lte: endDate }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + }) + + const nightRatio = totalActivity > 0 ? Number(nightActivity[0]?.count || 0) / totalActivity : 0 + const timePatterns = nightRatio > 0.2 ? 'UNUSUAL' : 'NORMAL' + + return { + dataAccess, + partnerships, + timePatterns, + } + } + + /** + * Генерирует прогнозы безопасности + */ + private static generateSecurityPredictions( + dataPoints: SecurityTrends['dataPoints'], + ): SecurityTrends['predictions'] { + if (dataPoints.length < 7) { + return { + nextPeriodRisk: 'MEDIUM', + expectedVolume: 0, + recommendedActions: ['Insufficient data for prediction'], + } + } + + // Анализ трендов за последнюю неделю + const recentPoints = dataPoints.slice(-7) + const avgRiskScore = recentPoints.reduce((sum, p) => sum + p.riskScore, 0) / recentPoints.length + const avgVolume = recentPoints.reduce((sum, p) => sum + p.totalAccesses, 0) / recentPoints.length + + // Расчет тренда + const firstHalf = recentPoints.slice(0, 3) + const secondHalf = recentPoints.slice(-3) + const firstAvgRisk = firstHalf.reduce((sum, p) => sum + p.riskScore, 0) / firstHalf.length + const secondAvgRisk = secondHalf.reduce((sum, p) => sum + p.riskScore, 0) / secondHalf.length + + const riskTrend = secondAvgRisk - firstAvgRisk + + // Определение уровня риска + let nextPeriodRisk: SecurityTrends['predictions']['nextPeriodRisk'] + if (avgRiskScore > 70 || riskTrend > 20) { + nextPeriodRisk = 'CRITICAL' + } else if (avgRiskScore > 40 || riskTrend > 10) { + nextPeriodRisk = 'HIGH' + } else if (avgRiskScore > 20 || riskTrend > 5) { + nextPeriodRisk = 'MEDIUM' + } else { + nextPeriodRisk = 'LOW' + } + + // Рекомендации + const recommendedActions: string[] = [] + if (riskTrend > 10) { + recommendedActions.push('Увеличить мониторинг активности пользователей') + } + if (avgRiskScore > 50) { + recommendedActions.push('Провести аудит учетных записей с высоким риском') + } + if (recentPoints.some((p) => p.alerts > 5)) { + recommendedActions.push('Усилить меры безопасности для критичных ресурсов') + } + + return { + nextPeriodRisk, + expectedVolume: Math.round(avgVolume * 1.1), // Прогноз роста на 10% + recommendedActions, + } + } +} \ No newline at end of file diff --git a/src/graphql/security/automated-threat-detection.ts b/src/graphql/security/automated-threat-detection.ts new file mode 100644 index 0000000..e17a24b --- /dev/null +++ b/src/graphql/security/automated-threat-detection.ts @@ -0,0 +1,1050 @@ +/** + * Система автоматического обнаружения угроз + * + * Использует машинное обучение и эвристические алгоритмы + * для выявления подозрительной активности и потенциальных угроз безопасности + */ + +import { PrismaClient } from '@prisma/client' +import { EventEmitter } from 'events' + +import { SecurityLogger } from '../../lib/security-logger' +import { RealTimeSecurityAlerts } from './real-time-security-alerts' +import { CommercialAccessType, ResourceType, SecurityAlert } from './types' + +/** + * Модель угрозы + */ +interface ThreatModel { + id: string + name: string + description: string + severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' + patterns: ThreatPattern[] + enabled: boolean + confidence: number // 0-100 + falsePositiveRate: number // 0-1 + lastUpdated: Date +} + +/** + * Паттерн угрозы + */ +interface ThreatPattern { + type: 'BEHAVIORAL' | 'VOLUMETRIC' | 'TEMPORAL' | 'CONTEXTUAL' + rule: string + weight: number + threshold: number + timeWindow: number // в минутах +} + +/** + * Профиль пользователя + */ +interface UserProfile { + userId: string + organizationType: string + organizationId: string + baseline: { + avgActionsPerHour: number + avgActionsPerDay: number + commonActions: Array<{ action: string; frequency: number }> + usualTimePattern: number[] // активность по часам (0-23) + commonResources: string[] + peakHours: number[] + } + anomalies: Array<{ + timestamp: Date + type: string + severity: string + description: string + confidence: number + }> + riskScore: number + lastAnalysis: Date +} + +/** + * Обнаруженная угроза + */ +interface DetectedThreat { + id: string + modelId: string + userId: string + organizationType: string + threatType: string + description: string + confidence: number + riskScore: number + indicators: string[] + evidence: Record + timestamp: Date + status: 'NEW' | 'INVESTIGATING' | 'CONFIRMED' | 'FALSE_POSITIVE' | 'RESOLVED' +} + +export class AutomatedThreatDetection extends EventEmitter { + private static instance: AutomatedThreatDetection + private isActive = false + private threatModels: ThreatModel[] = [] + private userProfiles = new Map() + private detectedThreats = new Map() + private analysisQueue: Array<{ userId: string; event: any }> = [] + private processing = false + + constructor(private prisma: PrismaClient) { + super() + this.initializeThreatModels() + } + + /** + * Получить singleton instance + */ + static getInstance(prisma: PrismaClient): AutomatedThreatDetection { + if (!AutomatedThreatDetection.instance) { + AutomatedThreatDetection.instance = new AutomatedThreatDetection(prisma) + } + return AutomatedThreatDetection.instance + } + + /** + * Запуск системы обнаружения угроз + */ + async start(): Promise { + if (this.isActive) { + return + } + + this.isActive = true + + // Загрузка базовых профилей пользователей + await this.loadUserProfiles() + + // Запуск обработки очереди анализа + this.startAnalysisLoop() + + // Периодическое обновление профилей (каждые 6 часов) + setInterval(() => { + this.updateUserProfiles() + }, 6 * 60 * 60 * 1000) + + // Периодическая очистка старых угроз (каждые 24 часа) + setInterval(() => { + this.cleanupOldThreats() + }, 24 * 60 * 60 * 1000) + + SecurityLogger.logSecurityInfo({ + message: 'Automated threat detection started', + modelsCount: this.threatModels.length, + profilesCount: this.userProfiles.size, + }) + + this.emit('threat_detection_started', { + timestamp: new Date(), + modelsLoaded: this.threatModels.length, + }) + } + + /** + * Остановка системы + */ + stop(): void { + this.isActive = false + this.removeAllListeners() + SecurityLogger.logSecurityInfo({ message: 'Automated threat detection stopped' }) + } + + /** + * Анализ события пользователя + */ + async analyzeUserEvent(event: { + userId: string + organizationType: string + organizationId: string + action: CommercialAccessType + resourceType: ResourceType + resourceId?: string + metadata?: Record + ipAddress?: string + userAgent?: string + timestamp: Date + }): Promise { + if (!this.isActive) { + return [] + } + + // Добавляем событие в очередь анализа + this.analysisQueue.push({ userId: event.userId, event }) + + // Немедленный анализ для критичных событий + if (this.isCriticalEvent(event)) { + return await this.performImmediateAnalysis(event) + } + + return [] + } + + /** + * Получение профиля пользователя + */ + getUserProfile(userId: string): UserProfile | undefined { + return this.userProfiles.get(userId) + } + + /** + * Получение обнаруженных угроз + */ + getDetectedThreats(filters?: { + userId?: string + severity?: string + status?: string + limit?: number + }): DetectedThreat[] { + let threats = Array.from(this.detectedThreats.values()) + + if (filters) { + if (filters.userId) { + threats = threats.filter((t) => t.userId === filters.userId) + } + if (filters.severity) { + const model = this.threatModels.find((m) => m.id === threats[0]?.modelId) + threats = threats.filter(() => model?.severity === filters.severity) + } + if (filters.status) { + threats = threats.filter((t) => t.status === filters.status) + } + if (filters.limit) { + threats = threats.slice(0, filters.limit) + } + } + + return threats.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) + } + + /** + * Обновление статуса угрозы + */ + async updateThreatStatus( + threatId: string, + status: DetectedThreat['status'], + feedback?: { accurate: boolean; comment?: string }, + ): Promise { + const threat = this.detectedThreats.get(threatId) + if (!threat) { + throw new Error(`Threat ${threatId} not found`) + } + + threat.status = status + + // Обновляем модель на основе обратной связи + if (feedback) { + await this.updateModelAccuracy(threat.modelId, feedback.accurate) + } + + SecurityLogger.logSecurityInfo({ + message: 'Threat status updated', + threatId, + newStatus: status, + feedback, + }) + + this.emit('threat_status_updated', { + threat, + oldStatus: threat.status, + newStatus: status, + feedback, + }) + } + + /** + * Инициализация моделей угроз + */ + private initializeThreatModels(): void { + this.threatModels = [ + { + id: 'data-scraping-detection', + name: 'Data Scraping Detection', + description: 'Detects systematic data extraction attempts', + severity: 'HIGH', + confidence: 85, + falsePositiveRate: 0.05, + enabled: true, + lastUpdated: new Date(), + patterns: [ + { + type: 'VOLUMETRIC', + rule: 'actions_per_minute > 10', + weight: 0.4, + threshold: 10, + timeWindow: 1, + }, + { + type: 'BEHAVIORAL', + rule: 'sequential_resource_access', + weight: 0.3, + threshold: 20, + timeWindow: 5, + }, + { + type: 'TEMPORAL', + rule: 'consistent_interval_pattern', + weight: 0.3, + threshold: 0.8, + timeWindow: 10, + }, + ], + }, + { + id: 'competitor-intelligence-gathering', + name: 'Competitor Intelligence Gathering', + description: 'Detects attempts to gather competitive intelligence', + severity: 'CRITICAL', + confidence: 75, + falsePositiveRate: 0.08, + enabled: true, + lastUpdated: new Date(), + patterns: [ + { + type: 'CONTEXTUAL', + rule: 'cross_organization_price_viewing', + weight: 0.5, + threshold: 5, + timeWindow: 60, + }, + { + type: 'BEHAVIORAL', + rule: 'recipe_focus_pattern', + weight: 0.3, + threshold: 3, + timeWindow: 30, + }, + { + type: 'TEMPORAL', + rule: 'off_hours_activity', + weight: 0.2, + threshold: 0.3, + timeWindow: 60, + }, + ], + }, + { + id: 'insider-threat-detection', + name: 'Insider Threat Detection', + description: 'Detects unusual behavior from trusted users', + severity: 'CRITICAL', + confidence: 70, + falsePositiveRate: 0.12, + enabled: true, + lastUpdated: new Date(), + patterns: [ + { + type: 'BEHAVIORAL', + rule: 'deviation_from_baseline > 3_sigma', + weight: 0.4, + threshold: 3, + timeWindow: 60, + }, + { + type: 'CONTEXTUAL', + rule: 'access_to_unusual_resources', + weight: 0.3, + threshold: 5, + timeWindow: 30, + }, + { + type: 'TEMPORAL', + rule: 'activity_time_anomaly', + weight: 0.3, + threshold: 0.5, + timeWindow: 60, + }, + ], + }, + { + id: 'account-compromise-detection', + name: 'Account Compromise Detection', + description: 'Detects potentially compromised user accounts', + severity: 'CRITICAL', + confidence: 80, + falsePositiveRate: 0.06, + enabled: true, + lastUpdated: new Date(), + patterns: [ + { + type: 'CONTEXTUAL', + rule: 'ip_geolocation_anomaly', + weight: 0.4, + threshold: 1, + timeWindow: 5, + }, + { + type: 'BEHAVIORAL', + rule: 'user_agent_change', + weight: 0.2, + threshold: 1, + timeWindow: 10, + }, + { + type: 'TEMPORAL', + rule: 'simultaneous_sessions', + weight: 0.4, + threshold: 2, + timeWindow: 1, + }, + ], + }, + ] + } + + /** + * Загрузка профилей пользователей + */ + private async loadUserProfiles(): Promise { + try { + // Получаем всех активных пользователей + const users = await this.prisma.user.findMany({ + include: { organization: true }, + where: { + // Только пользователи, активные за последние 30 дней + auditLogs: { + some: { + timestamp: { + gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), + }, + }, + }, + }, + }) + + for (const user of users) { + const profile = await this.buildUserProfile(user.id) + if (profile) { + this.userProfiles.set(user.id, profile) + } + } + + SecurityLogger.logSecurityInfo({ + message: 'User profiles loaded', + count: this.userProfiles.size, + }) + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'loadUserProfiles', + }) + } + } + + /** + * Построение профиля пользователя + */ + private async buildUserProfile(userId: string): Promise { + try { + const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) + + // Получаем активность пользователя за последние 30 дней + const auditLogs = await this.prisma.auditLog.findMany({ + where: { + userId, + timestamp: { gte: thirtyDaysAgo }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + orderBy: { timestamp: 'desc' }, + }) + + if (auditLogs.length === 0) { + return null + } + + const user = await this.prisma.user.findUnique({ + where: { id: userId }, + include: { organization: true }, + }) + + if (!user) return null + + // Вычисляем базовые показатели + const avgActionsPerHour = this.calculateAvgActionsPerHour(auditLogs) + const avgActionsPerDay = this.calculateAvgActionsPerDay(auditLogs) + const commonActions = this.getCommonActions(auditLogs) + const usualTimePattern = this.getTimePattern(auditLogs) + const commonResources = this.getCommonResources(auditLogs) + const peakHours = this.getPeakHours(usualTimePattern) + + return { + userId, + organizationType: user.organization?.type || 'UNKNOWN', + organizationId: user.organizationId || '', + baseline: { + avgActionsPerHour, + avgActionsPerDay, + commonActions, + usualTimePattern, + commonResources, + peakHours, + }, + anomalies: [], + riskScore: 0, + lastAnalysis: new Date(), + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'buildUserProfile', + userId, + }) + return null + } + } + + /** + * Запуск цикла анализа + */ + private startAnalysisLoop(): void { + setInterval(async () => { + if (this.processing || this.analysisQueue.length === 0) { + return + } + + this.processing = true + + try { + const batch = this.analysisQueue.splice(0, 50) // Обрабатываем по 50 событий за раз + + for (const { userId, event } of batch) { + await this.analyzeEvent(userId, event) + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'analysisLoop', + }) + } finally { + this.processing = false + } + }, 5000) // Каждые 5 секунд + } + + /** + * Анализ события + */ + private async analyzeEvent(userId: string, event: any): Promise { + const profile = this.userProfiles.get(userId) + if (!profile) { + // Создаем профиль на лету для нового пользователя + const newProfile = await this.buildUserProfile(userId) + if (newProfile) { + this.userProfiles.set(userId, newProfile) + } + return + } + + // Анализируем событие каждой моделью угрозы + for (const model of this.threatModels.filter((m) => m.enabled)) { + const threatScore = await this.evaluateThreatModel(model, profile, event) + + if (threatScore > 0.7) { + // Высокая вероятность угрозы + await this.createThreatAlert(model, profile, event, threatScore) + } + } + + // Обновляем профиль пользователя + this.updateProfileWithEvent(profile, event) + } + + /** + * Оценка модели угрозы + */ + private async evaluateThreatModel( + model: ThreatModel, + profile: UserProfile, + event: any, + ): Promise { + let totalScore = 0 + let totalWeight = 0 + + for (const pattern of model.patterns) { + const score = await this.evaluatePattern(pattern, profile, event) + totalScore += score * pattern.weight + totalWeight += pattern.weight + } + + return totalWeight > 0 ? totalScore / totalWeight : 0 + } + + /** + * Оценка паттерна угрозы + */ + private async evaluatePattern( + pattern: ThreatPattern, + profile: UserProfile, + event: any, + ): Promise { + const now = new Date() + const windowStart = new Date(now.getTime() - pattern.timeWindow * 60 * 1000) + + switch (pattern.type) { + case 'VOLUMETRIC': + return await this.evaluateVolumetricPattern(pattern, profile, event, windowStart) + + case 'BEHAVIORAL': + return await this.evaluateBehavioralPattern(pattern, profile, event, windowStart) + + case 'TEMPORAL': + return await this.evaluateTemporalPattern(pattern, profile, event, windowStart) + + case 'CONTEXTUAL': + return await this.evaluateContextualPattern(pattern, profile, event, windowStart) + + default: + return 0 + } + } + + /** + * Оценка объемного паттерна + */ + private async evaluateVolumetricPattern( + pattern: ThreatPattern, + profile: UserProfile, + event: any, + windowStart: Date, + ): Promise { + if (pattern.rule === 'actions_per_minute > 10') { + const recentActions = await this.prisma.auditLog.count({ + where: { + userId: profile.userId, + timestamp: { gte: windowStart }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + }) + + const actionsPerMinute = recentActions / pattern.timeWindow + return actionsPerMinute > pattern.threshold ? 1.0 : actionsPerMinute / pattern.threshold + } + + return 0 + } + + /** + * Оценка поведенческого паттерна + */ + private async evaluateBehavioralPattern( + pattern: ThreatPattern, + profile: UserProfile, + event: any, + windowStart: Date, + ): Promise { + if (pattern.rule === 'deviation_from_baseline > 3_sigma') { + const currentHour = new Date().getHours() + const expectedActivity = profile.baseline.usualTimePattern[currentHour] || 0 + const currentActivity = await this.getHourlyActivity(profile.userId, currentHour) + + if (expectedActivity === 0) return 0 + + const deviation = Math.abs(currentActivity - expectedActivity) / expectedActivity + return Math.min(1.0, deviation / 3) // 3 сигмы как порог + } + + if (pattern.rule === 'sequential_resource_access') { + // TODO: реализовать логику последовательного доступа к ресурсам + return 0 + } + + return 0 + } + + /** + * Оценка временного паттерна + */ + private async evaluateTemporalPattern( + pattern: ThreatPattern, + profile: UserProfile, + event: any, + windowStart: Date, + ): Promise { + if (pattern.rule === 'off_hours_activity') { + const hour = event.timestamp.getHours() + const isOffHours = hour < 6 || hour > 22 // Ночное время + const offHoursRatio = this.calculateOffHoursRatio(profile, windowStart) + + return isOffHours && offHoursRatio > pattern.threshold ? 1.0 : offHoursRatio / pattern.threshold + } + + return 0 + } + + /** + * Оценка контекстуального паттерна + */ + private async evaluateContextualPattern( + pattern: ThreatPattern, + profile: UserProfile, + event: any, + windowStart: Date, + ): Promise { + if (pattern.rule === 'cross_organization_price_viewing') { + // TODO: реализовать логику межорганизационного просмотра цен + return 0 + } + + if (pattern.rule === 'ip_geolocation_anomaly') { + // TODO: реализовать проверку геолокации IP + return 0 + } + + return 0 + } + + /** + * Проверка критичности события + */ + private isCriticalEvent(event: any): boolean { + const criticalActions = ['VIEW_RECIPE', 'BULK_EXPORT', 'VIEW_MARGINS'] + return criticalActions.includes(event.action) + } + + /** + * Немедленный анализ критичного события + */ + private async performImmediateAnalysis(event: any): Promise { + const threats: DetectedThreat[] = [] + const profile = this.userProfiles.get(event.userId) + + if (!profile) { + return threats + } + + // Анализируем только критичные модели + const criticalModels = this.threatModels.filter( + (m) => m.enabled && m.severity === 'CRITICAL', + ) + + for (const model of criticalModels) { + const threatScore = await this.evaluateThreatModel(model, profile, event) + + if (threatScore > 0.6) { + // Для критичных событий понижаем порог + const threat = await this.createThreatAlert(model, profile, event, threatScore) + threats.push(threat) + } + } + + return threats + } + + /** + * Создание алерта угрозы + */ + private async createThreatAlert( + model: ThreatModel, + profile: UserProfile, + event: any, + threatScore: number, + ): Promise { + const threat: DetectedThreat = { + id: `threat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + modelId: model.id, + userId: profile.userId, + organizationType: profile.organizationType, + threatType: model.name, + description: model.description, + confidence: Math.round(threatScore * model.confidence), + riskScore: Math.round(threatScore * 100), + indicators: this.extractThreatIndicators(model, profile, event), + evidence: { + event, + profileBaseline: profile.baseline, + modelPatterns: model.patterns, + threatScore, + }, + timestamp: new Date(), + status: 'NEW', + } + + this.detectedThreats.set(threat.id, threat) + + // Создаем security alert через существующую систему + const alertsInstance = RealTimeSecurityAlerts.getInstance(this.prisma) + await alertsInstance.generateAlert({ + type: 'THREAT_DETECTED', + severity: this.mapSeverity(model.severity), + userId: profile.userId, + message: `${model.name}: ${model.description}`, + metadata: { + threatId: threat.id, + confidence: threat.confidence, + riskScore: threat.riskScore, + indicators: threat.indicators, + }, + }) + + SecurityLogger.logSecurityAlert({ + id: threat.id, + type: 'THREAT_DETECTED', + severity: model.severity as any, + userId: profile.userId, + message: threat.description, + metadata: threat.evidence, + timestamp: threat.timestamp, + resolved: false, + }) + + this.emit('threat_detected', threat) + + return threat + } + + /** + * Извлечение индикаторов угрозы + */ + private extractThreatIndicators(model: ThreatModel, profile: UserProfile, event: any): string[] { + const indicators: string[] = [] + + // Базовые индикаторы + indicators.push(`Model: ${model.name}`) + indicators.push(`User: ${profile.userId}`) + indicators.push(`Organization: ${profile.organizationType}`) + indicators.push(`Action: ${event.action}`) + + // Специфичные индикаторы в зависимости от модели + if (model.id === 'data-scraping-detection') { + indicators.push('High frequency access detected') + indicators.push('Sequential resource pattern') + } + + if (model.id === 'competitor-intelligence-gathering') { + indicators.push('Cross-organization data access') + indicators.push('Price/recipe focus pattern') + } + + return indicators + } + + /** + * Маппинг уровня серьезности + */ + private mapSeverity(severity: string): SecurityAlert['severity'] { + const mapping: Record = { + LOW: 'LOW', + MEDIUM: 'MEDIUM', + HIGH: 'HIGH', + CRITICAL: 'CRITICAL', + } + return mapping[severity] || 'MEDIUM' + } + + /** + * Обновление профиля пользователя с новым событием + */ + private updateProfileWithEvent(profile: UserProfile, event: any): void { + // Простое обновление - в реальной системе это должно быть более сложным + profile.lastAnalysis = new Date() + } + + /** + * Вычисление средней активности в час + */ + private calculateAvgActionsPerHour(logs: any[]): number { + if (logs.length === 0) return 0 + + const hourlyCount = new Map() + + logs.forEach((log) => { + const hour = log.timestamp.toISOString().substring(0, 13) // YYYY-MM-DDTHH + hourlyCount.set(hour, (hourlyCount.get(hour) || 0) + 1) + }) + + const total = Array.from(hourlyCount.values()).reduce((sum, count) => sum + count, 0) + return total / hourlyCount.size + } + + /** + * Вычисление средней активности в день + */ + private calculateAvgActionsPerDay(logs: any[]): number { + if (logs.length === 0) return 0 + + const dailyCount = new Map() + + logs.forEach((log) => { + const day = log.timestamp.toISOString().substring(0, 10) // YYYY-MM-DD + dailyCount.set(day, (dailyCount.get(day) || 0) + 1) + }) + + const total = Array.from(dailyCount.values()).reduce((sum, count) => sum + count, 0) + return total / dailyCount.size + } + + /** + * Получение общих действий + */ + private getCommonActions(logs: any[]): Array<{ action: string; frequency: number }> { + const actionCount = new Map() + + logs.forEach((log) => { + const action = log.action.replace('DATA_ACCESS:', '') + actionCount.set(action, (actionCount.get(action) || 0) + 1) + }) + + return Array.from(actionCount.entries()) + .map(([action, count]) => ({ + action, + frequency: count / logs.length, + })) + .sort((a, b) => b.frequency - a.frequency) + .slice(0, 10) + } + + /** + * Получение паттерна времени + */ + private getTimePattern(logs: any[]): number[] { + const pattern = new Array(24).fill(0) + + logs.forEach((log) => { + const hour = log.timestamp.getHours() + pattern[hour]++ + }) + + // Нормализуем по общему количеству логов + return pattern.map((count) => count / logs.length) + } + + /** + * Получение общих ресурсов + */ + private getCommonResources(logs: any[]): string[] { + const resourceCount = new Map() + + logs.forEach((log) => { + if (log.resourceId) { + resourceCount.set(log.resourceId, (resourceCount.get(log.resourceId) || 0) + 1) + } + }) + + return Array.from(resourceCount.entries()) + .sort(([, a], [, b]) => b - a) + .slice(0, 20) + .map(([resource]) => resource) + } + + /** + * Получение пиковых часов + */ + private getPeakHours(timePattern: number[]): number[] { + const avg = timePattern.reduce((sum, val) => sum + val, 0) / timePattern.length + const threshold = avg * 1.5 // 150% от среднего + + return timePattern + .map((activity, hour) => ({ hour, activity })) + .filter(({ activity }) => activity > threshold) + .map(({ hour }) => hour) + } + + /** + * Получение активности по часам + */ + private async getHourlyActivity(userId: string, hour: number): Promise { + const startOfHour = new Date() + startOfHour.setHours(hour, 0, 0, 0) + const endOfHour = new Date() + endOfHour.setHours(hour, 59, 59, 999) + + return await this.prisma.auditLog.count({ + where: { + userId, + timestamp: { + gte: startOfHour, + lte: endOfHour, + }, + action: { startsWith: 'DATA_ACCESS:' }, + }, + }) + } + + /** + * Вычисление соотношения ночной активности + */ + private calculateOffHoursRatio(profile: UserProfile, since: Date): number { + const offHoursPattern = profile.baseline.usualTimePattern + .slice(0, 6) // 0-6 утра + .concat(profile.baseline.usualTimePattern.slice(22)) // 22-23 вечера + + const totalOffHours = offHoursPattern.reduce((sum, val) => sum + val, 0) + const totalActivity = profile.baseline.usualTimePattern.reduce((sum, val) => sum + val, 0) + + return totalActivity > 0 ? totalOffHours / totalActivity : 0 + } + + /** + * Обновление точности модели + */ + private async updateModelAccuracy(modelId: string, wasAccurate: boolean): Promise { + const model = this.threatModels.find((m) => m.id === modelId) + if (!model) return + + // Простое обновление confidence на основе обратной связи + if (wasAccurate) { + model.confidence = Math.min(100, model.confidence + 1) + model.falsePositiveRate = Math.max(0, model.falsePositiveRate - 0.01) + } else { + model.confidence = Math.max(0, model.confidence - 2) + model.falsePositiveRate = Math.min(1, model.falsePositiveRate + 0.02) + } + + model.lastUpdated = new Date() + + SecurityLogger.logSecurityInfo({ + message: 'Threat model accuracy updated', + modelId, + wasAccurate, + newConfidence: model.confidence, + newFalsePositiveRate: model.falsePositiveRate, + }) + } + + /** + * Обновление профилей пользователей + */ + private async updateUserProfiles(): Promise { + SecurityLogger.logSecurityInfo({ message: 'Updating user profiles...' }) + + for (const [userId] of this.userProfiles) { + try { + const updatedProfile = await this.buildUserProfile(userId) + if (updatedProfile) { + this.userProfiles.set(userId, updatedProfile) + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'updateUserProfile', + userId, + }) + } + } + + SecurityLogger.logSecurityInfo({ + message: 'User profiles updated', + count: this.userProfiles.size, + }) + } + + /** + * Очистка старых угроз + */ + private cleanupOldThreats(): void { + const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) + let cleaned = 0 + + for (const [id, threat] of this.detectedThreats) { + if (threat.timestamp < sevenDaysAgo && threat.status === 'RESOLVED') { + this.detectedThreats.delete(id) + cleaned++ + } + } + + SecurityLogger.logSecurityInfo({ + message: 'Old threats cleaned up', + cleaned, + remaining: this.detectedThreats.size, + }) + } +} \ No newline at end of file diff --git a/src/graphql/security/external-monitoring-integration.ts b/src/graphql/security/external-monitoring-integration.ts new file mode 100644 index 0000000..1277588 --- /dev/null +++ b/src/graphql/security/external-monitoring-integration.ts @@ -0,0 +1,902 @@ +/** + * Интеграция с внешними системами мониторинга + * + * Обеспечивает интеграцию системы безопасности SFERA с внешними SIEM/SOC системами, + * мониторингом инфраструктуры и системами уведомлений + */ + +import { EventEmitter } from 'events' +import { PrismaClient } from '@prisma/client' + +import { SecurityLogger } from '../../lib/security-logger' +import { SecurityAlert } from './types' + +/** + * Конфигурация интеграций + */ +interface IntegrationConfig { + siem: { + enabled: boolean + type: 'SPLUNK' | 'ELASTIC_SIEM' | 'QRADAR' | 'SENTINEL' + endpoint: string + apiKey: string + format: 'CEF' | 'JSON' | 'SYSLOG' + } + prometheus: { + enabled: boolean + pushGateway: string + jobName: string + instance: string + } + grafana: { + enabled: boolean + apiUrl: string + apiKey: string + dashboardId: string + } + datadog: { + enabled: boolean + apiKey: string + appKey: string + site: string + } + newrelic: { + enabled: boolean + licenseKey: string + appId: string + } + slack: { + enabled: boolean + webhookUrl: string + channel: string + } + teams: { + enabled: boolean + webhookUrl: string + } + pagerduty: { + enabled: boolean + integrationKey: string + severity: 'critical' | 'error' | 'warning' | 'info' + } + webhook: { + enabled: boolean + url: string + headers: Record + retries: number + } +} + +/** + * Метрики для экспорта + */ +interface SecurityMetrics { + timestamp: Date + totalAlerts: number + alertsBySeverity: Record + alertsByType: Record + activeThreats: number + riskScore: number + systemHealth: { + monitoring: number + alerts: number + audit: number + filtering: number + } + userActivity: { + totalAccesses: number + uniqueUsers: number + suspiciousActivity: number + } + performance: { + avgResponseTime: number + processingRate: number + errorRate: number + } +} + +/** + * События для внешних систем + */ +interface SecurityEvent { + id: string + timestamp: Date + eventType: 'ALERT_GENERATED' | 'THREAT_DETECTED' | 'SYSTEM_STATUS' | 'USER_ACTIVITY' + severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' + source: string + data: Record + tags: string[] +} + +export class ExternalMonitoringIntegration extends EventEmitter { + private static instance: ExternalMonitoringIntegration + private config: IntegrationConfig + private isActive = false + private metricsBuffer: SecurityMetrics[] = [] + private eventsBuffer: SecurityEvent[] = [] + + constructor(private prisma: PrismaClient) { + super() + this.loadConfiguration() + } + + /** + * Получить singleton instance + */ + static getInstance(prisma: PrismaClient): ExternalMonitoringIntegration { + if (!ExternalMonitoringIntegration.instance) { + ExternalMonitoringIntegration.instance = new ExternalMonitoringIntegration(prisma) + } + return ExternalMonitoringIntegration.instance + } + + /** + * Запуск интеграций + */ + async start(): Promise { + if (this.isActive) { + return + } + + this.isActive = true + + // Периодическая отправка метрик (каждые 30 секунд) + setInterval(() => { + this.pushMetrics() + }, 30000) + + // Периодическая отправка событий (каждые 5 секунд) + setInterval(() => { + this.pushEvents() + }, 5000) + + // Проверка состояния интеграций (каждые 5 минут) + setInterval(() => { + this.healthCheck() + }, 5 * 60 * 1000) + + SecurityLogger.logSecurityInfo({ + message: 'External monitoring integration started', + integrations: Object.keys(this.config).filter((key) => this.config[key as keyof IntegrationConfig]?.enabled), + }) + + this.emit('integration_started', { + timestamp: new Date(), + activeIntegrations: this.getActiveIntegrations(), + }) + } + + /** + * Остановка интеграций + */ + stop(): void { + this.isActive = false + this.removeAllListeners() + SecurityLogger.logSecurityInfo({ message: 'External monitoring integration stopped' }) + } + + /** + * Отправка алерта во внешние системы + */ + async sendSecurityAlert(alert: SecurityAlert): Promise { + if (!this.isActive) { + return + } + + const event: SecurityEvent = { + id: alert.id, + timestamp: alert.timestamp, + eventType: 'ALERT_GENERATED', + severity: alert.severity, + source: 'SFERA_SECURITY', + data: { + alertType: alert.type, + userId: alert.userId, + message: alert.message, + metadata: alert.metadata, + }, + tags: ['security', 'alert', alert.type.toLowerCase(), alert.severity.toLowerCase()], + } + + this.eventsBuffer.push(event) + + // Для критичных алертов - немедленная отправка + if (alert.severity === 'CRITICAL' || alert.severity === 'HIGH') { + await this.pushCriticalEvent(event) + } + } + + /** + * Отправка метрик безопасности + */ + async sendSecurityMetrics(metrics: SecurityMetrics): Promise { + if (!this.isActive) { + return + } + + this.metricsBuffer.push(metrics) + + // Если буфер переполнен - принудительная отправка + if (this.metricsBuffer.length > 100) { + await this.pushMetrics() + } + } + + /** + * Отправка события обнаружения угрозы + */ + async sendThreatDetection(threat: { + id: string + modelId: string + userId: string + threatType: string + confidence: number + riskScore: number + indicators: string[] + evidence: Record + }): Promise { + if (!this.isActive) { + return + } + + const event: SecurityEvent = { + id: threat.id, + timestamp: new Date(), + eventType: 'THREAT_DETECTED', + severity: threat.riskScore > 80 ? 'CRITICAL' : threat.riskScore > 60 ? 'HIGH' : 'MEDIUM', + source: 'SFERA_THREAT_DETECTION', + data: { + modelId: threat.modelId, + userId: threat.userId, + threatType: threat.threatType, + confidence: threat.confidence, + riskScore: threat.riskScore, + indicators: threat.indicators, + evidence: threat.evidence, + }, + tags: ['security', 'threat', 'detection', threat.threatType.toLowerCase()], + } + + this.eventsBuffer.push(event) + + // Для высокого risk score - немедленная отправка + if (threat.riskScore > 70) { + await this.pushCriticalEvent(event) + } + } + + /** + * Обновление статуса системы + */ + async sendSystemStatus(status: { + monitoring: { status: string; uptime: number; metrics: Record } + alerts: { status: string; uptime: number; metrics: Record } + audit: { status: string; uptime: number; metrics: Record } + dataFiltering: { status: string; uptime: number; metrics: Record } + overallHealth: number + }): Promise { + if (!this.isActive) { + return + } + + const event: SecurityEvent = { + id: `system-status-${Date.now()}`, + timestamp: new Date(), + eventType: 'SYSTEM_STATUS', + severity: status.overallHealth > 95 ? 'LOW' : status.overallHealth > 80 ? 'MEDIUM' : 'HIGH', + source: 'SFERA_SYSTEM', + data: status, + tags: ['system', 'status', 'health'], + } + + this.eventsBuffer.push(event) + } + + /** + * Загрузка конфигурации + */ + private loadConfiguration(): void { + this.config = { + siem: { + enabled: process.env.SIEM_INTEGRATION_ENABLED === 'true', + type: (process.env.SIEM_TYPE as any) || 'ELASTIC_SIEM', + endpoint: process.env.SIEM_ENDPOINT || '', + apiKey: process.env.SIEM_API_KEY || '', + format: (process.env.SIEM_FORMAT as any) || 'JSON', + }, + prometheus: { + enabled: process.env.PROMETHEUS_ENABLED === 'true', + pushGateway: process.env.PROMETHEUS_PUSH_GATEWAY || 'http://localhost:9091', + jobName: process.env.PROMETHEUS_JOB_NAME || 'sfera-security', + instance: process.env.PROMETHEUS_INSTANCE || 'sfera-api-01', + }, + grafana: { + enabled: process.env.GRAFANA_ENABLED === 'true', + apiUrl: process.env.GRAFANA_API_URL || '', + apiKey: process.env.GRAFANA_API_KEY || '', + dashboardId: process.env.GRAFANA_DASHBOARD_ID || '', + }, + datadog: { + enabled: process.env.DATADOG_ENABLED === 'true', + apiKey: process.env.DATADOG_API_KEY || '', + appKey: process.env.DATADOG_APP_KEY || '', + site: process.env.DATADOG_SITE || 'datadoghq.com', + }, + newrelic: { + enabled: process.env.NEWRELIC_ENABLED === 'true', + licenseKey: process.env.NEWRELIC_LICENSE_KEY || '', + appId: process.env.NEWRELIC_APP_ID || '', + }, + slack: { + enabled: process.env.SLACK_INTEGRATION_ENABLED === 'true', + webhookUrl: process.env.SLACK_WEBHOOK_URL || '', + channel: process.env.SLACK_CHANNEL || '#security-alerts', + }, + teams: { + enabled: process.env.TEAMS_INTEGRATION_ENABLED === 'true', + webhookUrl: process.env.TEAMS_WEBHOOK_URL || '', + }, + pagerduty: { + enabled: process.env.PAGERDUTY_ENABLED === 'true', + integrationKey: process.env.PAGERDUTY_INTEGRATION_KEY || '', + severity: (process.env.PAGERDUTY_SEVERITY as any) || 'error', + }, + webhook: { + enabled: process.env.WEBHOOK_INTEGRATION_ENABLED === 'true', + url: process.env.WEBHOOK_URL || '', + headers: JSON.parse(process.env.WEBHOOK_HEADERS || '{}'), + retries: parseInt(process.env.WEBHOOK_RETRIES || '3'), + }, + } + } + + /** + * Отправка метрик + */ + private async pushMetrics(): Promise { + if (this.metricsBuffer.length === 0) { + return + } + + const metrics = [...this.metricsBuffer] + this.metricsBuffer.length = 0 + + const promises: Promise[] = [] + + if (this.config.prometheus.enabled) { + promises.push(this.sendToPrometheus(metrics)) + } + + if (this.config.datadog.enabled) { + promises.push(this.sendToDatadog(metrics)) + } + + if (this.config.newrelic.enabled) { + promises.push(this.sendToNewRelic(metrics)) + } + + if (this.config.grafana.enabled) { + promises.push(this.sendToGrafana(metrics)) + } + + await Promise.allSettled(promises) + } + + /** + * Отправка событий + */ + private async pushEvents(): Promise { + if (this.eventsBuffer.length === 0) { + return + } + + const events = [...this.eventsBuffer] + this.eventsBuffer.length = 0 + + const promises: Promise[] = [] + + if (this.config.siem.enabled) { + promises.push(this.sendToSIEM(events)) + } + + if (this.config.webhook.enabled) { + promises.push(this.sendToWebhook(events)) + } + + await Promise.allSettled(promises) + } + + /** + * Немедленная отправка критичного события + */ + private async pushCriticalEvent(event: SecurityEvent): Promise { + const promises: Promise[] = [] + + if (this.config.slack.enabled) { + promises.push(this.sendToSlack([event])) + } + + if (this.config.teams.enabled) { + promises.push(this.sendToTeams([event])) + } + + if (this.config.pagerduty.enabled && event.severity === 'CRITICAL') { + promises.push(this.sendToPagerDuty([event])) + } + + await Promise.allSettled(promises) + } + + /** + * Отправка в SIEM систему + */ + private async sendToSIEM(events: SecurityEvent[]): Promise { + try { + const payload = events.map((event) => this.formatSIEMEvent(event)) + + const response = await fetch(this.config.siem.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.config.siem.apiKey}`, + }, + body: JSON.stringify({ events: payload }), + }) + + if (!response.ok) { + throw new Error(`SIEM API error: ${response.status} ${response.statusText}`) + } + + SecurityLogger.logSecurityInfo({ + message: 'Events sent to SIEM', + count: events.length, + siemType: this.config.siem.type, + }) + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'sendToSIEM', + eventsCount: events.length, + }) + } + } + + /** + * Отправка в Prometheus + */ + private async sendToPrometheus(metrics: SecurityMetrics[]): Promise { + try { + const latestMetrics = metrics[metrics.length - 1] + + const prometheusMetrics = this.formatPrometheusMetrics(latestMetrics) + + const response = await fetch(`${this.config.prometheus.pushGateway}/metrics/job/${this.config.prometheus.jobName}/instance/${this.config.prometheus.instance}`, { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: prometheusMetrics, + }) + + if (!response.ok) { + throw new Error(`Prometheus API error: ${response.status} ${response.statusText}`) + } + + SecurityLogger.logSecurityInfo({ + message: 'Metrics sent to Prometheus', + metricsCount: Object.keys(latestMetrics).length, + }) + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'sendToPrometheus', + metricsCount: metrics.length, + }) + } + } + + /** + * Отправка в Slack + */ + private async sendToSlack(events: SecurityEvent[]): Promise { + try { + for (const event of events.filter(e => e.severity === 'CRITICAL' || e.severity === 'HIGH')) { + const slackPayload = { + channel: this.config.slack.channel, + username: 'SFERA Security Bot', + icon_emoji: ':warning:', + attachments: [ + { + color: this.getSlackColor(event.severity), + title: `🚨 Security Alert: ${event.eventType}`, + text: `${event.data.message || 'Security event detected'}`, + fields: [ + { title: 'Severity', value: event.severity, short: true }, + { title: 'Source', value: event.source, short: true }, + { title: 'Event ID', value: event.id, short: true }, + { title: 'Timestamp', value: event.timestamp.toISOString(), short: true }, + ], + footer: 'SFERA Security System', + ts: Math.floor(event.timestamp.getTime() / 1000), + }, + ], + } + + const response = await fetch(this.config.slack.webhookUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(slackPayload), + }) + + if (!response.ok) { + throw new Error(`Slack API error: ${response.status} ${response.statusText}`) + } + } + + SecurityLogger.logSecurityInfo({ + message: 'Critical events sent to Slack', + count: events.length, + }) + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'sendToSlack', + eventsCount: events.length, + }) + } + } + + /** + * Отправка в Microsoft Teams + */ + private async sendToTeams(events: SecurityEvent[]): Promise { + try { + for (const event of events.filter(e => e.severity === 'CRITICAL' || e.severity === 'HIGH')) { + const teamsPayload = { + '@type': 'MessageCard', + '@context': 'http://schema.org/extensions', + themeColor: this.getTeamsColor(event.severity), + summary: `SFERA Security Alert: ${event.eventType}`, + sections: [ + { + activityTitle: `🚨 Security Alert: ${event.eventType}`, + activitySubtitle: event.data.message || 'Security event detected', + facts: [ + { name: 'Severity', value: event.severity }, + { name: 'Source', value: event.source }, + { name: 'Event ID', value: event.id }, + { name: 'Timestamp', value: event.timestamp.toISOString() }, + ], + markdown: true, + }, + ], + } + + const response = await fetch(this.config.teams.webhookUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(teamsPayload), + }) + + if (!response.ok) { + throw new Error(`Teams API error: ${response.status} ${response.statusText}`) + } + } + + SecurityLogger.logSecurityInfo({ + message: 'Critical events sent to Teams', + count: events.length, + }) + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'sendToTeams', + eventsCount: events.length, + }) + } + } + + /** + * Отправка в PagerDuty + */ + private async sendToPagerDuty(events: SecurityEvent[]): Promise { + try { + for (const event of events.filter(e => e.severity === 'CRITICAL')) { + const pagerDutyPayload = { + routing_key: this.config.pagerduty.integrationKey, + event_action: 'trigger', + payload: { + summary: `SFERA Security Alert: ${event.eventType}`, + severity: this.config.pagerduty.severity, + source: event.source, + timestamp: event.timestamp.toISOString(), + custom_details: { + event_id: event.id, + event_type: event.eventType, + data: event.data, + tags: event.tags, + }, + }, + } + + const response = await fetch('https://events.pagerduty.com/v2/enqueue', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(pagerDutyPayload), + }) + + if (!response.ok) { + throw new Error(`PagerDuty API error: ${response.status} ${response.statusText}`) + } + } + + SecurityLogger.logSecurityInfo({ + message: 'Critical events sent to PagerDuty', + count: events.length, + }) + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'sendToPagerDuty', + eventsCount: events.length, + }) + } + } + + /** + * Отправка в webhook + */ + private async sendToWebhook(events: SecurityEvent[]): Promise { + let attempts = 0 + const maxAttempts = this.config.webhook.retries + + while (attempts < maxAttempts) { + try { + const response = await fetch(this.config.webhook.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...this.config.webhook.headers, + }, + body: JSON.stringify({ + events, + timestamp: new Date().toISOString(), + source: 'SFERA_SECURITY', + }), + }) + + if (!response.ok) { + throw new Error(`Webhook API error: ${response.status} ${response.statusText}`) + } + + SecurityLogger.logSecurityInfo({ + message: 'Events sent to webhook', + count: events.length, + attempts: attempts + 1, + }) + + return + } catch (error) { + attempts++ + if (attempts >= maxAttempts) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'sendToWebhook', + eventsCount: events.length, + attempts, + }) + return + } + + // Экспоненциальная задержка + await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempts) * 1000)) + } + } + } + + /** + * Заглушки для других интеграций + */ + private async sendToDatadog(metrics: SecurityMetrics[]): Promise { + // TODO: Реализовать интеграцию с Datadog + console.log('📊 Datadog integration placeholder', metrics.length) + } + + private async sendToNewRelic(metrics: SecurityMetrics[]): Promise { + // TODO: Реализовать интеграцию с New Relic + console.log('📈 New Relic integration placeholder', metrics.length) + } + + private async sendToGrafana(metrics: SecurityMetrics[]): Promise { + // TODO: Реализовать интеграцию с Grafana + console.log('📋 Grafana integration placeholder', metrics.length) + } + + /** + * Форматирование события для SIEM + */ + private formatSIEMEvent(event: SecurityEvent): Record { + switch (this.config.siem.format) { + case 'CEF': + return this.formatCEF(event) + case 'SYSLOG': + return this.formatSyslog(event) + default: + return { + timestamp: event.timestamp.toISOString(), + id: event.id, + event_type: event.eventType, + severity: event.severity, + source: event.source, + data: event.data, + tags: event.tags, + } + } + } + + /** + * Форматирование в CEF + */ + private formatCEF(event: SecurityEvent): Record { + return { + cef_version: '0', + device_vendor: 'SFERA', + device_product: 'Security System', + device_version: '3.0.0', + signature_id: event.eventType, + name: event.id, + severity: this.mapCEFSeverity(event.severity), + extension: { + rt: event.timestamp.getTime(), + src: event.source, + msg: JSON.stringify(event.data), + }, + } + } + + /** + * Форматирование в Syslog + */ + private formatSyslog(event: SecurityEvent): Record { + const priority = this.mapSyslogPriority(event.severity) + return { + priority, + timestamp: event.timestamp.toISOString(), + hostname: 'sfera-security', + tag: 'SFERA-SEC', + message: `[${event.eventType}] ${event.id}: ${JSON.stringify(event.data)}`, + } + } + + /** + * Форматирование метрик для Prometheus + */ + private formatPrometheusMetrics(metrics: SecurityMetrics): string { + const lines: string[] = [] + const timestamp = metrics.timestamp.getTime() + + lines.push(`# HELP sfera_security_alerts_total Total number of security alerts`) + lines.push(`# TYPE sfera_security_alerts_total counter`) + lines.push(`sfera_security_alerts_total ${metrics.totalAlerts} ${timestamp}`) + + lines.push(`# HELP sfera_security_active_threats Current number of active threats`) + lines.push(`# TYPE sfera_security_active_threats gauge`) + lines.push(`sfera_security_active_threats ${metrics.activeThreats} ${timestamp}`) + + lines.push(`# HELP sfera_security_risk_score Current overall risk score`) + lines.push(`# TYPE sfera_security_risk_score gauge`) + lines.push(`sfera_security_risk_score ${metrics.riskScore} ${timestamp}`) + + lines.push(`# HELP sfera_security_user_accesses_total Total number of user data accesses`) + lines.push(`# TYPE sfera_security_user_accesses_total counter`) + lines.push(`sfera_security_user_accesses_total ${metrics.userActivity.totalAccesses} ${timestamp}`) + + return lines.join('\n') + } + + /** + * Получение активных интеграций + */ + private getActiveIntegrations(): string[] { + const active: string[] = [] + + Object.entries(this.config).forEach(([name, config]) => { + if (config.enabled) { + active.push(name) + } + }) + + return active + } + + /** + * Проверка здоровья интеграций + */ + private async healthCheck(): Promise { + const healthStatus: Record = {} + + // Проверяем доступность каждой интеграции + if (this.config.siem.enabled) { + healthStatus.siem = await this.checkSIEMHealth() + } + + if (this.config.prometheus.enabled) { + healthStatus.prometheus = await this.checkPrometheusHealth() + } + + if (this.config.slack.enabled) { + healthStatus.slack = await this.checkSlackHealth() + } + + SecurityLogger.logSecurityInfo({ + message: 'Integration health check completed', + status: healthStatus, + }) + + this.emit('health_check', { + timestamp: new Date(), + status: healthStatus, + }) + } + + /** + * Заглушки для проверки здоровья + */ + private async checkSIEMHealth(): Promise { + // TODO: Реализовать проверку SIEM + return true + } + + private async checkPrometheusHealth(): Promise { + // TODO: Реализовать проверку Prometheus + return true + } + + private async checkSlackHealth(): Promise { + // TODO: Реализовать проверку Slack + return true + } + + /** + * Вспомогательные функции для форматирования + */ + private getSlackColor(severity: string): string { + const colors: Record = { + CRITICAL: '#FF0000', + HIGH: '#FF8C00', + MEDIUM: '#FFD700', + LOW: '#00FF00', + } + return colors[severity] || '#808080' + } + + private getTeamsColor(severity: string): string { + const colors: Record = { + CRITICAL: 'FF0000', + HIGH: 'FF8C00', + MEDIUM: 'FFD700', + LOW: '00FF00', + } + return colors[severity] || '808080' + } + + private mapCEFSeverity(severity: string): number { + const mapping: Record = { + LOW: 3, + MEDIUM: 5, + HIGH: 7, + CRITICAL: 10, + } + return mapping[severity] || 5 + } + + private mapSyslogPriority(severity: string): number { + const mapping: Record = { + LOW: 22, // local0.info + MEDIUM: 20, // local0.warning + HIGH: 19, // local0.error + CRITICAL: 18, // local0.critical + } + return mapping[severity] || 20 + } +} \ No newline at end of file diff --git a/src/graphql/security/index.ts b/src/graphql/security/index.ts index 7841a24..53f9a12 100644 --- a/src/graphql/security/index.ts +++ b/src/graphql/security/index.ts @@ -38,6 +38,18 @@ export { listSecuredResolvers, } from './middleware' +// Расширенные компоненты Phase 3 +export { AdvancedAuditReporting } from './advanced-audit-reporting' +export { RealTimeSecurityAlerts } from './real-time-security-alerts' +export { AutomatedThreatDetection } from './automated-threat-detection' +export { ExternalMonitoringIntegration } from './external-monitoring-integration' + +// Security Dashboard GraphQL компоненты +export { + securityDashboardTypeDefs, + securityDashboardResolvers +} from './security-dashboard-graphql' + // Вспомогательные функции export { SecurityLogger } from '../../lib/security-logger' export { FEATURE_FLAGS, isFeatureEnabled, getActiveFeatures } from '../../config/features' diff --git a/src/graphql/security/real-time-security-alerts.ts b/src/graphql/security/real-time-security-alerts.ts new file mode 100644 index 0000000..3d90127 --- /dev/null +++ b/src/graphql/security/real-time-security-alerts.ts @@ -0,0 +1,882 @@ +/** + * Система real-time security alerts + * + * Обеспечивает мгновенные уведомления о критичных событиях безопасности, + * автоматическую эскалацию инцидентов и интеграцию с внешними системами + */ + +import { EventEmitter } from 'events' +import { PrismaClient } from '@prisma/client' +import { SecurityLogger } from '../../lib/security-logger' +import { CommercialAccessType, ResourceType, SecurityAlert } from './types' + +/** + * Конфигурация уведомлений + */ +interface NotificationConfig { + email: { + enabled: boolean + recipients: string[] + template: string + } + sms: { + enabled: boolean + phones: string[] + } + slack: { + enabled: boolean + webhook: string + channel: string + } + telegram: { + enabled: boolean + botToken: string + chatIds: string[] + } + push: { + enabled: boolean + serviceUrl: string + } +} + +/** + * Правила эскалации + */ +interface EscalationRule { + id: string + name: string + condition: { + alertType: SecurityAlert['type'] + severity: SecurityAlert['severity'] + userRole?: string + organizationType?: string + timeWindow?: number // минуты + threshold?: number // количество событий + } + actions: Array<{ + type: 'NOTIFY' | 'BLOCK_USER' | 'ESCALATE' | 'AUTO_RESOLVE' + delay: number // секунды + config: Record + }> + enabled: boolean +} + +/** + * Событие real-time алерта + */ +interface RealTimeAlertEvent { + alert: SecurityAlert + escalationLevel: number + autoActions: string[] + notificationsSent: string[] + timestamp: Date +} + +/** + * Конфигурация monitoring rules + */ +interface MonitoringRule { + id: string + name: string + description: string + type: 'RATE_LIMIT' | 'ANOMALY_DETECTION' | 'PATTERN_MATCHING' | 'THRESHOLD' + config: { + // Для RATE_LIMIT + maxRequests?: number + timeWindow?: number + + // Для ANOMALY_DETECTION + baseline?: number + deviation?: number + + // Для PATTERN_MATCHING + patterns?: string[] + + // Для THRESHOLD + field?: string + operator?: '>' | '<' | '=' | '!=' | '>=' | '<=' + value?: number | string + } + severity: SecurityAlert['severity'] + enabled: boolean +} + +export class RealTimeSecurityAlerts extends EventEmitter { + private static instance: RealTimeSecurityAlerts + private isActive = false + private monitoringRules: MonitoringRule[] = [] + private escalationRules: EscalationRule[] = [] + private notificationConfig: NotificationConfig + private activeAlerts = new Map() + private userSessions = new Map() + + constructor(private prisma: PrismaClient) { + super() + + // Конфигурация уведомлений по умолчанию + this.notificationConfig = { + email: { + enabled: process.env.SECURITY_EMAIL_ENABLED === 'true', + recipients: process.env.SECURITY_EMAIL_RECIPIENTS?.split(',') || [], + template: 'security-alert', + }, + sms: { + enabled: process.env.SECURITY_SMS_ENABLED === 'true', + phones: process.env.SECURITY_SMS_PHONES?.split(',') || [], + }, + slack: { + enabled: process.env.SECURITY_SLACK_ENABLED === 'true', + webhook: process.env.SECURITY_SLACK_WEBHOOK || '', + channel: process.env.SECURITY_SLACK_CHANNEL || '#security-alerts', + }, + telegram: { + enabled: process.env.SECURITY_TELEGRAM_ENABLED === 'true', + botToken: process.env.SECURITY_TELEGRAM_BOT_TOKEN || '', + chatIds: process.env.SECURITY_TELEGRAM_CHAT_IDS?.split(',') || [], + }, + push: { + enabled: process.env.SECURITY_PUSH_ENABLED === 'true', + serviceUrl: process.env.SECURITY_PUSH_SERVICE_URL || '', + }, + } + + this.initializeDefaultRules() + this.setupEventHandlers() + } + + /** + * Получить singleton instance + */ + static getInstance(prisma: PrismaClient): RealTimeSecurityAlerts { + if (!RealTimeSecurityAlerts.instance) { + RealTimeSecurityAlerts.instance = new RealTimeSecurityAlerts(prisma) + } + return RealTimeSecurityAlerts.instance + } + + /** + * Запуск real-time мониторинга + */ + async start(): Promise { + if (this.isActive) { + return + } + + this.isActive = true + await this.loadConfigurationFromDatabase() + + // Сбросить счетчики пользователей каждый час + setInterval(() => { + this.resetUserSessions() + }, 60 * 60 * 1000) + + SecurityLogger.logSecurityInfo({ + message: 'Real-time security monitoring started', + activeRules: this.monitoringRules.length, + escalationRules: this.escalationRules.length, + }) + + this.emit('monitoring_started', { + timestamp: new Date(), + rulesCount: this.monitoringRules.length, + }) + } + + /** + * Остановка мониторинга + */ + stop(): void { + this.isActive = false + this.removeAllListeners() + SecurityLogger.logSecurityInfo({ message: 'Real-time security monitoring stopped' }) + } + + /** + * Обработка события доступа к данным + */ + async processDataAccess(event: { + userId: string + organizationType: string + organizationId: string + action: CommercialAccessType + resourceType: ResourceType + resourceId?: string + metadata?: Record + ipAddress?: string + userAgent?: string + }): Promise { + if (!this.isActive) { + return + } + + try { + // Обновляем статистику пользователя + this.updateUserSession(event.userId) + + // Проверяем каждое правило мониторинга + for (const rule of this.monitoringRules.filter((r) => r.enabled)) { + const violation = await this.checkRule(rule, event) + if (violation) { + await this.handleRuleViolation(rule, event, violation) + } + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'processDataAccess', + userId: event.userId, + action: event.action, + }) + } + } + + /** + * Генерация нового алерта + */ + async generateAlert(params: { + type: SecurityAlert['type'] + severity: SecurityAlert['severity'] + userId: string + message: string + metadata: Record + autoResolve?: boolean + }): Promise { + const alert: SecurityAlert = { + id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + type: params.type, + severity: params.severity, + userId: params.userId, + message: params.message, + metadata: params.metadata, + timestamp: new Date(), + resolved: params.autoResolve || false, + } + + // Сохраняем в базу + await this.saveAlert(alert) + + // Добавляем в активные алерты + if (!alert.resolved) { + this.activeAlerts.set(alert.id, alert) + } + + // Запускаем процесс эскалации + await this.processEscalation(alert) + + // Отправляем событие + this.emit('alert_generated', { + alert, + timestamp: new Date(), + } as RealTimeAlertEvent) + + return alert + } + + /** + * Добавление нового правила мониторинга + */ + addMonitoringRule(rule: MonitoringRule): void { + this.monitoringRules.push(rule) + SecurityLogger.logSecurityInfo({ + message: 'New monitoring rule added', + ruleId: rule.id, + ruleName: rule.name, + type: rule.type, + }) + } + + /** + * Добавление правила эскалации + */ + addEscalationRule(rule: EscalationRule): void { + this.escalationRules.push(rule) + SecurityLogger.logSecurityInfo({ + message: 'New escalation rule added', + ruleId: rule.id, + ruleName: rule.name, + }) + } + + /** + * Получение статистики активных алертов + */ + getActiveAlertsStats(): { + total: number + bySeverity: Record + byType: Record + oldest: Date | null + } { + const alerts = Array.from(this.activeAlerts.values()) + + const bySeverity: Record = {} + const byType: Record = {} + let oldest: Date | null = null + + alerts.forEach((alert) => { + bySeverity[alert.severity] = (bySeverity[alert.severity] || 0) + 1 + byType[alert.type] = (byType[alert.type] || 0) + 1 + + if (!oldest || alert.timestamp < oldest) { + oldest = alert.timestamp + } + }) + + return { + total: alerts.length, + bySeverity, + byType, + oldest, + } + } + + /** + * Инициализация правил по умолчанию + */ + private initializeDefaultRules(): void { + // Rate limiting правила + this.monitoringRules = [ + { + id: 'rate-limit-view-price', + name: 'Price View Rate Limit', + description: 'Detect excessive price viewing', + type: 'RATE_LIMIT', + config: { + maxRequests: 100, + timeWindow: 60, // 1 час + }, + severity: 'HIGH', + enabled: true, + }, + { + id: 'rate-limit-view-recipe', + name: 'Recipe View Rate Limit', + description: 'Detect excessive recipe viewing', + type: 'RATE_LIMIT', + config: { + maxRequests: 50, + timeWindow: 60, + }, + severity: 'HIGH', + enabled: true, + }, + { + id: 'anomaly-night-activity', + name: 'Night Activity Anomaly', + description: 'Detect unusual night activity', + type: 'ANOMALY_DETECTION', + config: { + baseline: 5, // Обычное количество действий ночью + deviation: 3, // Отклонение в разах + }, + severity: 'MEDIUM', + enabled: true, + }, + ] + + // Правила эскалации + this.escalationRules = [ + { + id: 'critical-immediate', + name: 'Critical Immediate Escalation', + condition: { + alertType: 'UNAUTHORIZED_ATTEMPT', + severity: 'CRITICAL', + }, + actions: [ + { + type: 'NOTIFY', + delay: 0, + config: { channels: ['email', 'sms', 'slack'] }, + }, + { + type: 'BLOCK_USER', + delay: 30, + config: { duration: 3600 }, // 1 час + }, + ], + enabled: true, + }, + { + id: 'high-escalate', + name: 'High Severity Escalation', + condition: { + alertType: 'EXCESSIVE_ACCESS', + severity: 'HIGH', + }, + actions: [ + { + type: 'NOTIFY', + delay: 0, + config: { channels: ['slack', 'telegram'] }, + }, + { + type: 'ESCALATE', + delay: 300, // 5 минут + config: { level: 2 }, + }, + ], + enabled: true, + }, + ] + } + + /** + * Настройка обработчиков событий + */ + private setupEventHandlers(): void { + this.on('alert_generated', async (event: RealTimeAlertEvent) => { + SecurityLogger.logSecurityAlert(event.alert) + }) + + this.on('escalation_triggered', async (data: { alert: SecurityAlert; level: number }) => { + SecurityLogger.logSecurityInfo({ + message: 'Alert escalation triggered', + alertId: data.alert.id, + level: data.level, + }) + }) + } + + /** + * Обновление сессии пользователя + */ + private updateUserSession(userId: string): void { + const now = new Date() + const session = this.userSessions.get(userId) + + if (!session || now.getTime() - session.lastReset.getTime() > 60 * 60 * 1000) { + // Новая сессия или прошел час + this.userSessions.set(userId, { + actions: 1, + lastReset: now, + }) + } else { + session.actions++ + } + } + + /** + * Сброс сессий пользователей + */ + private resetUserSessions(): void { + const now = new Date() + for (const [userId, session] of this.userSessions.entries()) { + if (now.getTime() - session.lastReset.getTime() > 60 * 60 * 1000) { + this.userSessions.delete(userId) + } + } + } + + /** + * Проверка правила мониторинга + */ + private async checkRule( + rule: MonitoringRule, + event: { + userId: string + action: CommercialAccessType + organizationType: string + [key: string]: unknown + }, + ): Promise | null> { + switch (rule.type) { + case 'RATE_LIMIT': + return this.checkRateLimit(rule, event) + + case 'ANOMALY_DETECTION': + return await this.checkAnomaly(rule, event) + + case 'PATTERN_MATCHING': + return this.checkPattern(rule, event) + + case 'THRESHOLD': + return this.checkThreshold(rule, event) + + default: + return null + } + } + + /** + * Проверка rate limit + */ + private checkRateLimit( + rule: MonitoringRule, + event: { userId: string; action: CommercialAccessType }, + ): Record | null { + const session = this.userSessions.get(event.userId) + if (!session) return null + + const maxRequests = rule.config.maxRequests || 100 + + if (session.actions > maxRequests) { + return { + currentActions: session.actions, + maxAllowed: maxRequests, + timeWindow: rule.config.timeWindow || 60, + } + } + + return null + } + + /** + * Проверка аномалий + */ + private async checkAnomaly( + rule: MonitoringRule, + event: { userId: string; action: CommercialAccessType }, + ): Promise | null> { + if (rule.id === 'anomaly-night-activity') { + const hour = new Date().getHours() + if (hour >= 0 && hour <= 6) { + const session = this.userSessions.get(event.userId) + const actions = session?.actions || 0 + const baseline = rule.config.baseline || 5 + const deviation = rule.config.deviation || 3 + + if (actions > baseline * deviation) { + return { + nightActions: actions, + baseline, + threshold: baseline * deviation, + } + } + } + } + + return null + } + + /** + * Проверка паттернов + */ + private checkPattern( + rule: MonitoringRule, + event: { [key: string]: unknown }, + ): Record | null { + // Простая реализация pattern matching + const patterns = rule.config.patterns || [] + + for (const pattern of patterns) { + const regex = new RegExp(pattern as string) + const eventString = JSON.stringify(event) + + if (regex.test(eventString)) { + return { + matchedPattern: pattern, + eventData: eventString, + } + } + } + + return null + } + + /** + * Проверка threshold + */ + private checkThreshold( + rule: MonitoringRule, + event: { [key: string]: unknown }, + ): Record | null { + const field = rule.config.field + const operator = rule.config.operator + const expectedValue = rule.config.value + + if (!field || !operator || expectedValue === undefined) { + return null + } + + const actualValue = event[field] + let violated = false + + switch (operator) { + case '>': + violated = Number(actualValue) > Number(expectedValue) + break + case '<': + violated = Number(actualValue) < Number(expectedValue) + break + case '>=': + violated = Number(actualValue) >= Number(expectedValue) + break + case '<=': + violated = Number(actualValue) <= Number(expectedValue) + break + case '=': + violated = actualValue === expectedValue + break + case '!=': + violated = actualValue !== expectedValue + break + } + + if (violated) { + return { + field, + actualValue, + expectedValue, + operator, + } + } + + return null + } + + /** + * Обработка нарушения правила + */ + private async handleRuleViolation( + rule: MonitoringRule, + event: { userId: string; action: CommercialAccessType; [key: string]: unknown }, + violation: Record, + ): Promise { + const alert = await this.generateAlert({ + type: 'RULE_VIOLATION', + severity: rule.severity, + userId: event.userId, + message: `Rule violation: ${rule.name}`, + metadata: { + ruleId: rule.id, + ruleName: rule.name, + ruleType: rule.type, + violation, + event, + }, + }) + + SecurityLogger.logSecurityInfo({ + message: 'Monitoring rule violation detected', + ruleId: rule.id, + userId: event.userId, + alertId: alert.id, + }) + } + + /** + * Обработка эскалации алерта + */ + private async processEscalation(alert: SecurityAlert): Promise { + const matchingRules = this.escalationRules.filter((rule) => + rule.enabled && this.matchesEscalationCondition(rule.condition, alert) + ) + + for (const rule of matchingRules) { + for (const action of rule.actions) { + // Задержка перед выполнением действия + setTimeout(async () => { + await this.executeEscalationAction(action, alert, rule) + }, action.delay * 1000) + } + } + } + + /** + * Проверка соответствия условиям эскалации + */ + private matchesEscalationCondition( + condition: EscalationRule['condition'], + alert: SecurityAlert, + ): boolean { + if (condition.alertType && condition.alertType !== alert.type) { + return false + } + + if (condition.severity && condition.severity !== alert.severity) { + return false + } + + // TODO: Добавить другие проверки условий + + return true + } + + /** + * Выполнение действия эскалации + */ + private async executeEscalationAction( + action: EscalationRule['actions'][0], + alert: SecurityAlert, + rule: EscalationRule, + ): Promise { + try { + switch (action.type) { + case 'NOTIFY': + await this.sendNotifications(alert, action.config.channels as string[]) + break + + case 'BLOCK_USER': + await this.blockUser(alert.userId, action.config.duration as number) + break + + case 'ESCALATE': + await this.escalateAlert(alert, action.config.level as number) + break + + case 'AUTO_RESOLVE': + await this.autoResolveAlert(alert.id) + break + } + + SecurityLogger.logSecurityInfo({ + message: 'Escalation action executed', + actionType: action.type, + alertId: alert.id, + ruleId: rule.id, + }) + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'executeEscalationAction', + actionType: action.type, + alertId: alert.id, + }) + } + } + + /** + * Отправка уведомлений + */ + private async sendNotifications(alert: SecurityAlert, channels: string[]): Promise { + const promises: Promise[] = [] + + for (const channel of channels) { + switch (channel) { + case 'email': + if (this.notificationConfig.email.enabled) { + promises.push(this.sendEmailNotification(alert)) + } + break + + case 'sms': + if (this.notificationConfig.sms.enabled) { + promises.push(this.sendSmsNotification(alert)) + } + break + + case 'slack': + if (this.notificationConfig.slack.enabled) { + promises.push(this.sendSlackNotification(alert)) + } + break + + case 'telegram': + if (this.notificationConfig.telegram.enabled) { + promises.push(this.sendTelegramNotification(alert)) + } + break + + case 'push': + if (this.notificationConfig.push.enabled) { + promises.push(this.sendPushNotification(alert)) + } + break + } + } + + await Promise.allSettled(promises) + } + + /** + * Отправка email уведомления + */ + private async sendEmailNotification(alert: SecurityAlert): Promise { + // TODO: Реализовать отправку email + console.log(`📧 Email notification sent for alert ${alert.id}`) + } + + /** + * Отправка SMS уведомления + */ + private async sendSmsNotification(alert: SecurityAlert): Promise { + // TODO: Реализовать отправку SMS + console.log(`📱 SMS notification sent for alert ${alert.id}`) + } + + /** + * Отправка Slack уведомления + */ + private async sendSlackNotification(alert: SecurityAlert): Promise { + // TODO: Реализовать отправку в Slack + console.log(`💬 Slack notification sent for alert ${alert.id}`) + } + + /** + * Отправка Telegram уведомления + */ + private async sendTelegramNotification(alert: SecurityAlert): Promise { + // TODO: Реализовать отправку в Telegram + console.log(`✈️ Telegram notification sent for alert ${alert.id}`) + } + + /** + * Отправка push уведомления + */ + private async sendPushNotification(alert: SecurityAlert): Promise { + // TODO: Реализовать отправку push уведомлений + console.log(`🔔 Push notification sent for alert ${alert.id}`) + } + + /** + * Блокировка пользователя + */ + private async blockUser(userId: string, duration: number): Promise { + // TODO: Реализовать блокировку пользователя + console.log(`🚫 User ${userId} blocked for ${duration} seconds`) + } + + /** + * Эскалация алерта + */ + private async escalateAlert(alert: SecurityAlert, level: number): Promise { + this.emit('escalation_triggered', { alert, level }) + } + + /** + * Автоматическое разрешение алерта + */ + private async autoResolveAlert(alertId: string): Promise { + this.activeAlerts.delete(alertId) + + await this.prisma.securityAlert.update({ + where: { id: alertId }, + data: { resolved: true }, + }) + } + + /** + * Сохранение алерта в базе данных + */ + private async saveAlert(alert: SecurityAlert): Promise { + try { + await this.prisma.securityAlert.create({ + data: { + id: alert.id, + type: alert.type, + severity: alert.severity, + userId: alert.userId, + message: alert.message, + metadata: alert.metadata, + timestamp: alert.timestamp, + resolved: alert.resolved, + }, + }) + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'saveAlert', + alertId: alert.id, + }) + } + } + + /** + * Загрузка конфигурации из базы данных + */ + private async loadConfigurationFromDatabase(): Promise { + // TODO: Реализовать загрузку правил из базы данных + SecurityLogger.logSecurityInfo({ + message: 'Security configuration loaded', + monitoringRules: this.monitoringRules.length, + escalationRules: this.escalationRules.length, + }) + } +} \ No newline at end of file diff --git a/src/graphql/security/security-dashboard-graphql.ts b/src/graphql/security/security-dashboard-graphql.ts new file mode 100644 index 0000000..ae49df8 --- /dev/null +++ b/src/graphql/security/security-dashboard-graphql.ts @@ -0,0 +1,766 @@ +/** + * GraphQL резолверы для security dashboard + * + * Предоставляет API для получения данных безопасности, + * метрик, алертов и аналитики для административной панели + */ + +import { PrismaClient } from '@prisma/client' +import { GraphQLError } from 'graphql' + +import { AdvancedAuditReporting } from './advanced-audit-reporting' +import { RealTimeSecurityAlerts } from './real-time-security-alerts' +import { CommercialDataAudit } from './commercial-data-audit' +import { SecurityLogger } from '../../lib/security-logger' + +interface SecurityDashboardContext { + user: { + id: string + organizationId: string + organizationType: string + } + prisma: PrismaClient +} + +/** + * Типы для GraphQL схемы security dashboard + */ +export const securityDashboardTypeDefs = ` + extend type Query { + securityMetrics(period: String!): SecurityMetrics + securityAlerts(limit: Int, offset: Int, severity: String): SecurityAlertsList + userAnalytics(userId: String!, period: String): UserAnalytics + organizationSecurityReport(organizationId: String!, startDate: String!, endDate: String!): OrganizationReport + securityTrends(days: Int): SecurityTrends + activeSecurityThreats: ActiveThreats + securitySystemStatus: SecuritySystemStatus + } + + extend type Mutation { + resolveSecurityAlert(alertId: String!): Boolean + updateSecurityRule(ruleId: String!, config: JSON!): Boolean + generateSecurityReport(params: SecurityReportParams!): SecurityReportResult + } + + extend type Subscription { + securityAlertsStream: SecurityAlert + securityMetricsStream: SecurityMetrics + } + + type SecurityMetrics { + totalAccesses: Int! + uniqueUsers: Int! + topActions: [ActionCount!]! + organizationBreakdown: [OrganizationCount!]! + timeDistribution: [HourlyCount!]! + suspiciousActivity: Int! + resolvedAlerts: Int! + activeAlerts: Int! + riskScore: Float! + } + + type ActionCount { + action: String! + count: Int! + } + + type OrganizationCount { + type: String! + count: Int! + } + + type HourlyCount { + hour: Int! + count: Int! + } + + type SecurityAlertsList { + alerts: [SecurityAlert!]! + totalCount: Int! + hasMore: Boolean! + } + + type SecurityAlert { + id: String! + type: String! + severity: String! + userId: String! + message: String! + metadata: JSON! + timestamp: String! + resolved: Boolean! + resolvedBy: String + resolvedAt: String + } + + type UserAnalytics { + userId: String! + organizationType: String! + organizationId: String! + totalAccesses: Int! + uniqueResources: Int! + lastActivity: String! + riskScore: Float! + activities: [UserActivity!]! + anomalies: [SecurityAnomaly!]! + } + + type UserActivity { + action: String! + resourceType: String! + count: Int! + avgPerHour: Float! + maxPerHour: Int! + timeRange: TimeRange! + } + + type TimeRange { + start: String! + end: String! + } + + type SecurityAnomaly { + type: String! + description: String! + severity: String! + timestamp: String! + } + + type OrganizationReport { + organizationId: String! + organizationType: String! + period: TimeRange! + users: Int! + totalActivity: Int! + breakdown: ActivityBreakdown! + compliance: ComplianceStatus! + alerts: AlertSummary! + } + + type ActivityBreakdown { + viewing: Int! + modifying: Int! + exporting: Int! + } + + type ComplianceStatus { + dataAccess: String! + partnerships: String! + timePatterns: String! + } + + type AlertSummary { + generated: Int! + resolved: Int! + highPriority: Int! + } + + type SecurityTrends { + period: TimeRange! + dataPoints: [TrendDataPoint!]! + predictions: SecurityPredictions! + } + + type TrendDataPoint { + timestamp: String! + totalAccesses: Int! + uniqueUsers: Int! + alerts: Int! + riskScore: Float! + } + + type SecurityPredictions { + nextPeriodRisk: String! + expectedVolume: Int! + recommendedActions: [String!]! + } + + type ActiveThreats { + highRiskUsers: [ThreatUser!]! + suspiciousPatterns: [ThreatPattern!]! + criticalAlerts: [SecurityAlert!]! + compromisedSessions: Int! + blockedIps: [String!]! + } + + type ThreatUser { + userId: String! + organizationType: String! + riskScore: Float! + lastActivity: String! + threatIndicators: [String!]! + } + + type ThreatPattern { + pattern: String! + description: String! + occurrences: Int! + affectedUsers: Int! + firstSeen: String! + lastSeen: String! + } + + type SecuritySystemStatus { + monitoring: SystemComponent! + alerts: SystemComponent! + audit: SystemComponent! + dataFiltering: SystemComponent! + uptime: Float! + version: String! + lastHealthCheck: String! + } + + type SystemComponent { + status: String! + version: String! + uptime: Float! + metrics: JSON! + } + + input SecurityReportParams { + startDate: String! + endDate: String! + organizationId: String + userId: String + reportType: String! + format: String! + } + + type SecurityReportResult { + reportId: String! + status: String! + downloadUrl: String + generatedAt: String! + expiresAt: String! + } +` + +/** + * GraphQL резолверы для security dashboard + */ +export const securityDashboardResolvers = { + Query: { + /** + * Получение основных метрик безопасности + */ + securityMetrics: async ( + _: unknown, + { period }: { period: string }, + context: SecurityDashboardContext, + ) => { + await validateSecurityAccess(context) + + try { + const { startDate, endDate } = parsePeriod(period) + const metrics = await AdvancedAuditReporting.generateSecurityReport( + context.prisma, + startDate, + endDate, + ) + + // Вычисляем общий risk score + const riskScore = calculateOverallRiskScore(metrics) + + return { + ...metrics, + riskScore, + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'securityMetrics', + userId: context.user.id, + period, + }) + throw new GraphQLError('Failed to fetch security metrics') + } + }, + + /** + * Получение списка алертов безопасности + */ + securityAlerts: async ( + _: unknown, + { + limit = 50, + offset = 0, + severity, + }: { limit?: number; offset?: number; severity?: string }, + context: SecurityDashboardContext, + ) => { + await validateSecurityAccess(context) + + try { + const whereClause: any = {} + if (severity) { + whereClause.severity = severity + } + + const [alerts, totalCount] = await Promise.all([ + context.prisma.securityAlert.findMany({ + where: whereClause, + orderBy: { timestamp: 'desc' }, + take: limit, + skip: offset, + }), + context.prisma.securityAlert.count({ where: whereClause }), + ]) + + return { + alerts: alerts.map(formatSecurityAlert), + totalCount, + hasMore: offset + limit < totalCount, + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'securityAlerts', + userId: context.user.id, + }) + throw new GraphQLError('Failed to fetch security alerts') + } + }, + + /** + * Получение аналитики пользователя + */ + userAnalytics: async ( + _: unknown, + { userId, period }: { userId: string; period: string }, + context: SecurityDashboardContext, + ) => { + await validateSecurityAccess(context) + + try { + const { startDate } = parsePeriod(period) + const analytics = await AdvancedAuditReporting.generateUserAnalytics( + context.prisma, + userId, + startDate, + ) + + return { + ...analytics, + lastActivity: analytics.lastActivity.toISOString(), + activities: analytics.activities.map((activity) => ({ + ...activity, + timeRange: { + start: activity.timeRange.start.toISOString(), + end: activity.timeRange.end.toISOString(), + }, + })), + anomalies: analytics.anomalies.map((anomaly) => ({ + ...anomaly, + timestamp: anomaly.timestamp.toISOString(), + })), + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'userAnalytics', + userId: context.user.id, + targetUserId: userId, + }) + throw new GraphQLError('Failed to fetch user analytics') + } + }, + + /** + * Получение отчета по организации + */ + organizationSecurityReport: async ( + _: unknown, + { + organizationId, + startDate, + endDate, + }: { organizationId: string; startDate: string; endDate: string }, + context: SecurityDashboardContext, + ) => { + await validateSecurityAccess(context) + await validateOrganizationAccess(context, organizationId) + + try { + const report = await AdvancedAuditReporting.generateOrganizationReport( + context.prisma, + organizationId, + new Date(startDate), + new Date(endDate), + ) + + return { + ...report, + period: { + start: report.period.start.toISOString(), + end: report.period.end.toISOString(), + }, + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'organizationSecurityReport', + userId: context.user.id, + organizationId, + }) + throw new GraphQLError('Failed to generate organization report') + } + }, + + /** + * Получение трендов безопасности + */ + securityTrends: async ( + _: unknown, + { days = 30 }: { days?: number }, + context: SecurityDashboardContext, + ) => { + await validateSecurityAccess(context) + + try { + const trends = await AdvancedAuditReporting.analyzeSecurityTrends(context.prisma, days) + + return { + period: { + start: trends.period.start.toISOString(), + end: trends.period.end.toISOString(), + }, + dataPoints: trends.dataPoints.map((point) => ({ + ...point, + timestamp: point.timestamp.toISOString(), + })), + predictions: trends.predictions, + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'securityTrends', + userId: context.user.id, + days, + }) + throw new GraphQLError('Failed to analyze security trends') + } + }, + + /** + * Получение активных угроз + */ + activeSecurityThreats: async (_: unknown, __: unknown, context: SecurityDashboardContext) => { + await validateSecurityAccess(context) + + try { + const [highRiskUsers, criticalAlerts] = await Promise.all([ + getHighRiskUsers(context.prisma), + context.prisma.securityAlert.findMany({ + where: { + severity: 'CRITICAL', + resolved: false, + }, + orderBy: { timestamp: 'desc' }, + take: 10, + }), + ]) + + const suspiciousPatterns = await detectSuspiciousPatterns(context.prisma) + + return { + highRiskUsers, + suspiciousPatterns, + criticalAlerts: criticalAlerts.map(formatSecurityAlert), + compromisedSessions: 0, // TODO: реализовать подсчет скомпрометированных сессий + blockedIps: [], // TODO: реализовать список заблокированных IP + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'activeSecurityThreats', + userId: context.user.id, + }) + throw new GraphQLError('Failed to fetch active threats') + } + }, + + /** + * Получение статуса системы безопасности + */ + securitySystemStatus: async (_: unknown, __: unknown, context: SecurityDashboardContext) => { + await validateSecurityAccess(context) + + try { + const alertsInstance = RealTimeSecurityAlerts.getInstance(context.prisma) + const alertsStats = alertsInstance.getActiveAlertsStats() + + return { + monitoring: { + status: 'ONLINE', + version: '3.0.0', + uptime: 99.9, + metrics: { + activeRules: 15, + processedEvents: 1000, + }, + }, + alerts: { + status: 'ONLINE', + version: '3.0.0', + uptime: 99.8, + metrics: { + activeAlerts: alertsStats.total, + processedToday: 100, + }, + }, + audit: { + status: 'ONLINE', + version: '3.0.0', + uptime: 99.99, + metrics: { + logsToday: 5000, + avgResponseTime: 50, + }, + }, + dataFiltering: { + status: 'ONLINE', + version: '3.0.0', + uptime: 99.95, + metrics: { + filteredToday: 2000, + cacheHitRate: 85, + }, + }, + uptime: 99.9, + version: '3.0.0', + lastHealthCheck: new Date().toISOString(), + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'securitySystemStatus', + userId: context.user.id, + }) + throw new GraphQLError('Failed to get system status') + } + }, + }, + + Mutation: { + /** + * Разрешение алерта безопасности + */ + resolveSecurityAlert: async ( + _: unknown, + { alertId }: { alertId: string }, + context: SecurityDashboardContext, + ) => { + await validateSecurityAccess(context) + + try { + await CommercialDataAudit.resolveAlert(context.prisma, alertId, context.user.id) + return true + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'resolveSecurityAlert', + userId: context.user.id, + alertId, + }) + return false + } + }, + + /** + * Обновление правила безопасности + */ + updateSecurityRule: async ( + _: unknown, + { ruleId, config }: { ruleId: string; config: Record }, + context: SecurityDashboardContext, + ) => { + await validateSecurityAccess(context) + + try { + // TODO: реализовать обновление правил безопасности + SecurityLogger.logSecurityInfo({ + message: 'Security rule updated', + ruleId, + config, + userId: context.user.id, + }) + return true + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'updateSecurityRule', + userId: context.user.id, + ruleId, + }) + return false + } + }, + + /** + * Генерация отчета безопасности + */ + generateSecurityReport: async ( + _: unknown, + { params }: { params: Record }, + context: SecurityDashboardContext, + ) => { + await validateSecurityAccess(context) + + try { + const reportId = `report-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` + + // TODO: реализовать генерацию отчетов + SecurityLogger.logSecurityInfo({ + message: 'Security report generation started', + reportId, + params, + userId: context.user.id, + }) + + return { + reportId, + status: 'GENERATING', + downloadUrl: null, + generatedAt: new Date().toISOString(), + expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24 часа + } + } catch (error) { + SecurityLogger.logSecurityError(error as Error, { + operation: 'generateSecurityReport', + userId: context.user.id, + params, + }) + throw new GraphQLError('Failed to generate report') + } + }, + }, + + // TODO: реализовать Subscription резолверы для real-time обновлений +} + +/** + * Проверка доступа к security dashboard + */ +async function validateSecurityAccess(context: SecurityDashboardContext): Promise { + // Проверяем, что пользователь имеет права администратора системы безопасности + const user = await context.prisma.user.findUnique({ + where: { id: context.user.id }, + include: { organization: true }, + }) + + if (!user) { + throw new GraphQLError('User not found', { extensions: { code: 'UNAUTHENTICATED' } }) + } + + // TODO: добавить проверку роли администратора безопасности + // Пока разрешаем доступ только администраторам организаций + if (!user.organization || !['ADMIN', 'SECURITY_ADMIN'].includes(user.role || '')) { + throw new GraphQLError('Insufficient permissions for security dashboard', { + extensions: { code: 'FORBIDDEN' }, + }) + } +} + +/** + * Проверка доступа к данным организации + */ +async function validateOrganizationAccess( + context: SecurityDashboardContext, + organizationId: string, +): Promise { + // Суперадмины могут видеть все организации + if (context.user.organizationType === 'SFERA_ADMIN') { + return + } + + // Обычные администраторы могут видеть только свою организацию + if (context.user.organizationId !== organizationId) { + throw new GraphQLError('Access denied to organization data', { + extensions: { code: 'FORBIDDEN' }, + }) + } +} + +/** + * Парсинг периода времени + */ +function parsePeriod(period: string): { startDate: Date; endDate: Date } { + const endDate = new Date() + let startDate: Date + + switch (period) { + case '1h': + startDate = new Date(endDate.getTime() - 60 * 60 * 1000) + break + case '24h': + startDate = new Date(endDate.getTime() - 24 * 60 * 60 * 1000) + break + case '7d': + startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000) + break + case '30d': + startDate = new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000) + break + default: + startDate = new Date(endDate.getTime() - 24 * 60 * 60 * 1000) + } + + return { startDate, endDate } +} + +/** + * Вычисление общего risk score + */ +function calculateOverallRiskScore(metrics: any): number { + let score = 0 + + // Базовый score от объема активности + score += Math.min(20, metrics.totalAccesses / 1000) + + // Score от подозрительной активности + score += Math.min(30, metrics.suspiciousActivity * 5) + + // Score от активных алертов + score += Math.min(25, metrics.activeAlerts * 2) + + // Score от нераспределенности активности + const orgTypes = metrics.organizationBreakdown.length + if (orgTypes < 2) score += 15 // Активность только в одном типе организации + + return Math.min(100, Math.max(0, score)) +} + +/** + * Форматирование алерта безопасности + */ +function formatSecurityAlert(alert: any): any { + return { + ...alert, + timestamp: alert.timestamp.toISOString(), + resolvedAt: alert.resolvedAt?.toISOString() || null, + } +} + +/** + * Получение пользователей с высоким риском + */ +async function getHighRiskUsers(prisma: PrismaClient): Promise { + // TODO: реализовать логику определения пользователей с высоким риском + return [ + { + userId: 'high-risk-user-1', + organizationType: 'WHOLESALE', + riskScore: 85.5, + lastActivity: new Date().toISOString(), + threatIndicators: ['EXCESSIVE_ACCESS', 'UNUSUAL_TIME_PATTERN', 'MULTIPLE_IP_ADDRESSES'], + }, + ] +} + +/** + * Обнаружение подозрительных паттернов + */ +async function detectSuspiciousPatterns(prisma: PrismaClient): Promise { + // TODO: реализовать обнаружение паттернов + return [ + { + pattern: 'Rapid sequential price viewing', + description: 'Users viewing prices in rapid succession across multiple organizations', + occurrences: 15, + affectedUsers: 3, + firstSeen: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + lastSeen: new Date().toISOString(), + }, + ] +} \ No newline at end of file