
- Обновлены тесты безопасности для всех ролей (SELLER, WHOLESALE, FULFILLMENT, LOGIST) - Улучшен мониторинг и аудит доступа к коммерческим данным - Добавлена интеграция с внешними системами мониторинга - Исправлены ESLint предупреждения в компонентах поставщика - Обновлены middleware для безопасности GraphQL резолверов 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
728 lines
22 KiB
TypeScript
728 lines
22 KiB
TypeScript
/**
|
||
* Расширенная система отчетности по аудиту безопасности
|
||
*
|
||
* Предоставляет детальные отчеты, аналитику и визуализацию
|
||
* активности пользователей в системе для выявления паттернов и угроз
|
||
*/
|
||
|
||
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<SecurityMetrics> {
|
||
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<Array<{ hour: number; count: bigint }>>`
|
||
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<UserAnalytics> {
|
||
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<OrganizationReport> {
|
||
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<SecurityTrends> {
|
||
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<number> {
|
||
try {
|
||
const hourlyData = await prisma.$queryRaw<Array<{ count: bigint }>>`
|
||
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<UserAnalytics['anomalies']> {
|
||
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<OrganizationReport['compliance']> {
|
||
// Анализ доступа к данным
|
||
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<Array<{ count: bigint }>>`
|
||
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,
|
||
}
|
||
}
|
||
} |