Files
sfera-new/src/graphql/security/commercial-data-audit.ts
Veronika Smirnova 6e3201f491 feat: Phase 1 - Implementation of Data Security Infrastructure
Implemented comprehensive data security infrastructure for SFERA platform:

## Security Classes Created:
- `SupplyDataFilter`: Role-based data filtering for supply orders
- `ParticipantIsolation`: Data isolation between competing organizations
- `RecipeAccessControl`: Protection of production recipes and trade secrets
- `CommercialDataAudit`: Audit logging and suspicious activity detection
- `SecurityLogger`: Centralized security event logging system

## Infrastructure Components:
- Feature flags system for gradual security rollout
- Database migrations for audit logging (AuditLog, SecurityAlert models)
- Secure resolver wrapper for automatic GraphQL security
- TypeScript interfaces and type safety throughout

## Security Features:
- Role-based access control (SELLER, WHOLESALE, FULFILLMENT, LOGIST)
- Commercial data protection between competitors
- Production recipe confidentiality
- Audit trail for all data access
- Real-time security monitoring and alerts
- Rate limiting and suspicious activity detection

## Implementation Notes:
- All console logging replaced with centralized security logger
- Comprehensive TypeScript typing with no explicit 'any' types
- Modular architecture following SFERA coding standards
- Feature flag controlled rollout for safe deployment

This completes Phase 1 of the security implementation plan.
Next phases will integrate these classes into existing GraphQL resolvers.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 17:51:02 +03:00

440 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Система аудита доступа к коммерческим данным
*
* Логирует все обращения к чувствительной коммерческой информации,
* отслеживает подозрительную активность и генерирует алерты
*/
import { PrismaClient } from '@prisma/client'
import { SecurityLogger } from '../../lib/security-logger'
import { CommercialAccessType, ResourceType, SecurityAlert, AuditParams, AlertThresholds } from './types'
/**
* Статистика активности пользователя
*/
interface UserActivityStats {
userId: string
organizationType: string
action: CommercialAccessType
count: number
timeframe: string
lastAccess: Date
}
export class CommercialDataAudit {
/**
* Пороговые значения для различных типов доступа
*/
private static readonly ALERT_THRESHOLDS: AlertThresholds = {
VIEW_PRICE: {
perHour: 100, // Максимум 100 просмотров цен в час
perDay: 500, // Максимум 500 просмотров цен в день
},
VIEW_RECIPE: {
perHour: 50, // Максимум 50 просмотров рецептур в час
perDay: 200, // Максимум 200 просмотров рецептур в день
},
VIEW_CONTACTS: {
perHour: 30, // Максимум 30 просмотров контактов в час
perDay: 100, // Максимум 100 просмотров контактов в день
},
VIEW_MARGINS: {
perHour: 20, // Максимум 20 просмотров маржинальности в час
perDay: 80, // Максимум 80 просмотров маржинальности в день
},
BULK_EXPORT: {
perHour: 5, // Максимум 5 экспортов в час
perDay: 20, // Максимум 20 экспортов в день
},
}
/**
* Логирует доступ к коммерческим данным
*/
static async logAccess(prisma: PrismaClient, params: AuditParams): Promise<void> {
try {
// Создаем запись в журнале аудита
await prisma.auditLog.create({
data: {
userId: params.userId,
organizationType: params.organizationType,
action: `DATA_ACCESS:${params.action}`,
resourceType: params.resourceType,
resourceId: params.resourceId || null,
metadata: params.metadata || {},
ipAddress: params.ipAddress,
userAgent: params.userAgent,
},
})
// Логируем через SecurityLogger
SecurityLogger.logDataAccess({
userId: params.userId,
organizationType: params.organizationType,
action: params.action,
resource: params.resourceType,
resourceId: params.resourceId,
filtered: false,
ipAddress: params.ipAddress,
userAgent: params.userAgent,
})
// Проверяем на подозрительную активность
await this.checkSuspiciousActivity(prisma, params)
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'logAccess',
params,
})
}
}
/**
* Логирует попытку несанкционированного доступа
*/
static async logUnauthorizedAccess(
prisma: PrismaClient,
params: {
userId: string
organizationType: string
resourceType: ResourceType
resourceId: string
reason: string
ipAddress?: string
userAgent?: string
},
): Promise<void> {
try {
// Записываем в журнал аудита
await prisma.auditLog.create({
data: {
userId: params.userId,
organizationType: params.organizationType,
action: 'UNAUTHORIZED_ACCESS_ATTEMPT',
resourceType: params.resourceType,
resourceId: params.resourceId,
metadata: {
reason: params.reason,
blocked: true,
},
ipAddress: params.ipAddress,
userAgent: params.userAgent,
},
})
// Генерируем алерт безопасности
const alert: SecurityAlert = {
id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
type: 'UNAUTHORIZED_ATTEMPT',
severity: 'HIGH',
userId: params.userId,
message: `Unauthorized access attempt to ${params.resourceType} ${params.resourceId}`,
metadata: {
resourceType: params.resourceType,
resourceId: params.resourceId,
reason: params.reason,
organizationType: params.organizationType,
ipAddress: params.ipAddress,
userAgent: params.userAgent,
},
timestamp: new Date(),
resolved: false,
}
await this.processSecurityAlert(prisma, alert)
SecurityLogger.logAccessAttempt({
userId: params.userId,
organizationType: params.organizationType,
resource: params.resourceType,
resourceId: params.resourceId,
success: false,
reason: params.reason,
ipAddress: params.ipAddress,
userAgent: params.userAgent,
})
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'logUnauthorizedAccess',
params,
})
}
}
/**
* Проверяет подозрительную активность пользователя
*/
private static async checkSuspiciousActivity(prisma: PrismaClient, params: AuditParams): Promise<void> {
const threshold = this.ALERT_THRESHOLDS[params.action]
if (!threshold) return
try {
// Считаем активность за последний час
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000)
const hourlyCount = await this.getActivityCount(prisma, params.userId, params.action, oneHourAgo)
// Проверяем превышение почасового лимита
if (hourlyCount > threshold.perHour) {
await this.sendExcessiveAccessAlert(prisma, {
userId: params.userId,
organizationType: params.organizationType,
action: params.action,
count: hourlyCount,
threshold: threshold.perHour,
timeframe: '1 hour',
severity: 'HIGH',
})
}
// Считаем активность за последние 24 часа
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000)
const dailyCount = await this.getActivityCount(prisma, params.userId, params.action, oneDayAgo)
// Проверяем превышение дневного лимита
if (dailyCount > threshold.perDay) {
await this.sendExcessiveAccessAlert(prisma, {
userId: params.userId,
organizationType: params.organizationType,
action: params.action,
count: dailyCount,
threshold: threshold.perDay,
timeframe: '24 hours',
severity: 'MEDIUM',
})
}
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'checkSuspiciousActivity',
userId: params.userId,
action: params.action,
})
}
}
/**
* Получает количество действий пользователя за период
*/
private static async getActivityCount(
prisma: PrismaClient,
userId: string,
action: CommercialAccessType,
since: Date,
): Promise<number> {
return await prisma.auditLog.count({
where: {
userId,
action: { contains: action },
timestamp: { gte: since },
},
})
}
/**
* Отправляет алерт о чрезмерной активности
*/
private static async sendExcessiveAccessAlert(
prisma: PrismaClient,
params: {
userId: string
organizationType: string
action: CommercialAccessType
count: number
threshold: number
timeframe: string
severity: 'MEDIUM' | 'HIGH'
},
): Promise<void> {
const alert: SecurityAlert = {
id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
type: 'EXCESSIVE_ACCESS',
severity: params.severity,
userId: params.userId,
message:
`Excessive ${params.action} activity: ${params.count} actions in ` +
`${params.timeframe} (threshold: ${params.threshold})`,
metadata: {
action: params.action,
count: params.count,
threshold: params.threshold,
timeframe: params.timeframe,
organizationType: params.organizationType,
},
timestamp: new Date(),
resolved: false,
}
await this.processSecurityAlert(prisma, alert)
SecurityLogger.logSuspiciousActivity({
userId: params.userId,
organizationType: params.organizationType,
activity: params.action,
count: params.count,
timeframe: params.timeframe,
threshold: params.threshold,
})
}
/**
* Обрабатывает алерт безопасности
*/
private static async processSecurityAlert(prisma: PrismaClient, alert: SecurityAlert): Promise<void> {
try {
// Сохраняем алерт в базу данных
await 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,
},
})
// Логируем алерт
SecurityLogger.logSecurityAlert(alert)
// Для критичных алертов - немедленная отправка уведомлений
if (alert.severity === 'HIGH' || alert.severity === 'CRITICAL') {
await this.sendImmediateNotification(alert)
}
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'processSecurityAlert',
alertId: alert.id,
})
}
}
/**
* Отправляет немедленное уведомление о критичном алерте
*/
private static async sendImmediateNotification(alert: SecurityAlert): Promise<void> {
// TODO: Реализовать отправку уведомлений
// - Email администраторам
// - SMS для критичных алертов
// - Slack/Teams уведомления
// - Push уведомления в мобильное приложение
console.error(`🚨 CRITICAL SECURITY ALERT: ${alert.message}`, {
alertId: alert.id,
userId: alert.userId,
type: alert.type,
severity: alert.severity,
timestamp: alert.timestamp,
})
}
/**
* Получает статистику активности для пользователя
*/
static async getUserActivityStats(
prisma: PrismaClient,
userId: string,
period: '1h' | '24h' | '7d' = '24h',
): Promise<UserActivityStats[]> {
const periodMs = {
'1h': 60 * 60 * 1000,
'24h': 24 * 60 * 60 * 1000,
'7d': 7 * 24 * 60 * 60 * 1000,
}
const since = new Date(Date.now() - periodMs[period])
try {
const rawStats = await prisma.auditLog.groupBy({
by: ['userId', 'organizationType', 'action'],
where: {
userId,
timestamp: { gte: since },
action: { startsWith: 'DATA_ACCESS:' },
},
_count: {
action: true,
},
_max: {
timestamp: true,
},
})
return rawStats.map((stat) => ({
userId: stat.userId,
organizationType: (stat.organizationType as string) || 'UNKNOWN',
action: stat.action.replace('DATA_ACCESS:', '') as CommercialAccessType,
count: stat._count.action,
timeframe: period,
lastAccess: stat._max.timestamp || new Date(),
}))
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'getUserActivityStats',
userId,
period,
})
return []
}
}
/**
* Получает активные алерты безопасности
*/
static async getActiveAlerts(prisma: PrismaClient, limit: number = 50): Promise<SecurityAlert[]> {
try {
const alerts = await prisma.securityAlert.findMany({
where: {
resolved: false,
},
orderBy: {
timestamp: 'desc',
},
take: limit,
})
return alerts.map((alert) => ({
id: alert.id,
type: alert.type as SecurityAlert['type'],
severity: alert.severity as SecurityAlert['severity'],
userId: alert.userId,
message: alert.message,
metadata: alert.metadata as Record<string, unknown>,
timestamp: alert.timestamp,
resolved: alert.resolved,
}))
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'getActiveAlerts',
})
return []
}
}
/**
* Помечает алерт как разрешенный
*/
static async resolveAlert(prisma: PrismaClient, alertId: string, resolvedBy: string): Promise<void> {
try {
await prisma.securityAlert.update({
where: { id: alertId },
data: {
resolved: true,
metadata: {
resolvedBy,
resolvedAt: new Date(),
},
},
})
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'resolveAlert',
alertId,
resolvedBy,
})
}
}
}