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>
This commit is contained in:
842
docs/development/SUPPLY_DATA_SECURITY_IMPLEMENTATION_PLAN.md
Normal file
842
docs/development/SUPPLY_DATA_SECURITY_IMPLEMENTATION_PLAN.md
Normal file
@ -0,0 +1,842 @@
|
||||
# ПЛАН РЕАЛИЗАЦИИ БЕЗОПАСНОСТИ ДАННЫХ В ПОСТАВКАХ
|
||||
|
||||
## 🎯 ОБЗОР ПЛАНА
|
||||
|
||||
План поэтапной реализации системы безопасности данных в поставках с минимальными рисками для существующей функциональности.
|
||||
|
||||
### КЛЮЧЕВЫЕ ПРИНЦИПЫ РЕАЛИЗАЦИИ:
|
||||
|
||||
1. **Постепенное внедрение** - каждая фаза независима и тестируема
|
||||
2. **Обратная совместимость** - не ломаем существующий функционал
|
||||
3. **Мониторинг на каждом этапе** - отслеживаем влияние изменений
|
||||
4. **Откат при проблемах** - возможность быстро вернуться к предыдущей версии
|
||||
|
||||
## 📅 TIMELINE И ПРИОРИТЕТЫ
|
||||
|
||||
| Фаза | Название | Длительность | Приоритет | Риски |
|
||||
| --------- | --------------------------- | ------------- | -------------- | ------- |
|
||||
| **1** | Подготовка инфраструктуры | 2-3 дня | 🔴 Критический | Низкие |
|
||||
| **2** | Базовые классы безопасности | 3-4 дня | 🔴 Критический | Низкие |
|
||||
| **3** | Обновление резолверов | 5-7 дней | 🔴 Критический | Средние |
|
||||
| **4** | Система аудита | 2-3 дня | 🟡 Высокий | Низкие |
|
||||
| **5** | Тестирование | 3-4 дня | 🟡 Высокий | Низкие |
|
||||
| **6** | Оптимизация | 2-3 дня | 🟢 Средний | Низкие |
|
||||
| **ИТОГО** | | **17-24 дня** | | |
|
||||
|
||||
## 🛠️ ФАЗА 1: ПОДГОТОВКА ИНФРАСТРУКТУРЫ (2-3 дня)
|
||||
|
||||
### Цель:
|
||||
|
||||
Подготовить кодовую базу для внедрения безопасности без нарушения работы системы.
|
||||
|
||||
### Задачи:
|
||||
|
||||
#### 1.1 Создание структуры директорий
|
||||
|
||||
```bash
|
||||
src/
|
||||
├── graphql/
|
||||
│ ├── security/ # Новая папка для безопасности
|
||||
│ │ ├── index.ts # Экспорт всех модулей
|
||||
│ │ ├── supply-data-filter.ts
|
||||
│ │ ├── participant-isolation.ts
|
||||
│ │ ├── recipe-access-control.ts
|
||||
│ │ ├── commercial-data-audit.ts
|
||||
│ │ └── types.ts # Типы для безопасности
|
||||
│ └── resolvers/
|
||||
│ └── supply-orders/ # Рефакторинг резолверов
|
||||
│ ├── queries.ts
|
||||
│ ├── mutations.ts
|
||||
│ └── helpers.ts
|
||||
```
|
||||
|
||||
#### 1.2 Создание feature flag для постепенного внедрения
|
||||
|
||||
```typescript
|
||||
// src/config/features.ts
|
||||
export const FEATURE_FLAGS = {
|
||||
SUPPLY_DATA_SECURITY: {
|
||||
enabled: process.env.ENABLE_SUPPLY_SECURITY === 'true',
|
||||
auditEnabled: process.env.ENABLE_SECURITY_AUDIT === 'true',
|
||||
strictMode: process.env.SECURITY_STRICT_MODE === 'true',
|
||||
},
|
||||
}
|
||||
|
||||
// Использование в коде
|
||||
if (FEATURE_FLAGS.SUPPLY_DATA_SECURITY.enabled) {
|
||||
// Новая логика безопасности
|
||||
return SupplyDataFilter.filterByRole(data, userRole)
|
||||
} else {
|
||||
// Старая логика
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3 Настройка логирования для отладки
|
||||
|
||||
```typescript
|
||||
// src/lib/security-logger.ts
|
||||
export class SecurityLogger {
|
||||
private static readonly DEBUG = process.env.SECURITY_DEBUG === 'true'
|
||||
|
||||
static logDataAccess(params: { userId: string; action: string; resource: string; filtered: boolean }) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[SECURITY]', {
|
||||
...params,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.4 Создание миграции БД для аудита (без применения)
|
||||
|
||||
```sql
|
||||
-- prisma/migrations/add_audit_log_table.sql
|
||||
CREATE TABLE "AuditLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"organizationType" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"resourceType" TEXT NOT NULL,
|
||||
"resourceId" TEXT,
|
||||
"metadata" JSONB DEFAULT '{}',
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "AuditLog_userId_idx" ON "AuditLog"("userId");
|
||||
CREATE INDEX "AuditLog_timestamp_idx" ON "AuditLog"("timestamp");
|
||||
CREATE INDEX "AuditLog_action_idx" ON "AuditLog"("action");
|
||||
```
|
||||
|
||||
### Результаты фазы 1:
|
||||
|
||||
- ✅ Структура готова для новых классов
|
||||
- ✅ Feature flags позволяют безопасное тестирование
|
||||
- ✅ Логирование настроено для отладки
|
||||
- ✅ Миграция БД подготовлена
|
||||
|
||||
## 🔐 ФАЗА 2: БАЗОВЫЕ КЛАССЫ БЕЗОПАСНОСТИ (3-4 дня)
|
||||
|
||||
### Цель:
|
||||
|
||||
Реализовать основные классы фильтрации данных с полным покрытием тестами.
|
||||
|
||||
### Задачи:
|
||||
|
||||
#### 2.1 Создание типов безопасности
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/types.ts
|
||||
export interface SecurityContext {
|
||||
user: {
|
||||
id: string
|
||||
organizationId: string
|
||||
organizationType: OrganizationType
|
||||
}
|
||||
ipAddress?: string
|
||||
userAgent?: string
|
||||
}
|
||||
|
||||
export interface FilteredData<T> {
|
||||
data: T
|
||||
filtered: boolean
|
||||
removedFields: string[]
|
||||
}
|
||||
|
||||
export type DataAccessLevel = 'FULL' | 'PARTIAL' | 'NONE'
|
||||
```
|
||||
|
||||
#### 2.2 Реализация SupplyDataFilter
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/supply-data-filter.ts
|
||||
export class SupplyDataFilter {
|
||||
// Статические методы для фильтрации
|
||||
static filterSupplyOrder(order: SupplyOrder, context: SecurityContext): FilteredData<Partial<SupplyOrder>> {
|
||||
const { organizationType, organizationId } = context.user
|
||||
|
||||
// Логика фильтрации по ролям
|
||||
switch (organizationType) {
|
||||
case 'SELLER':
|
||||
return this.filterForSeller(order, organizationId)
|
||||
case 'WHOLESALE':
|
||||
return this.filterForWholesale(order, organizationId)
|
||||
case 'FULFILLMENT':
|
||||
return this.filterForFulfillment(order, organizationId)
|
||||
case 'LOGIST':
|
||||
return this.filterForLogist(order, organizationId)
|
||||
default:
|
||||
throw new GraphQLError('Unauthorized organization type')
|
||||
}
|
||||
}
|
||||
|
||||
// Приватные методы для каждой роли
|
||||
private static filterForSeller(/*...*/) {
|
||||
/*...*/
|
||||
}
|
||||
private static filterForWholesale(/*...*/) {
|
||||
/*...*/
|
||||
}
|
||||
private static filterForFulfillment(/*...*/) {
|
||||
/*...*/
|
||||
}
|
||||
private static filterForLogist(/*...*/) {
|
||||
/*...*/
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 Реализация ParticipantIsolation
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/participant-isolation.ts
|
||||
export class ParticipantIsolation {
|
||||
static async checkAccess(
|
||||
prisma: PrismaClient,
|
||||
context: SecurityContext,
|
||||
resourceId: string,
|
||||
resourceType: 'SUPPLY_ORDER' | 'PRODUCT' | 'SERVICE',
|
||||
): Promise<boolean> {
|
||||
// Проверка доступа к ресурсу
|
||||
const hasAccess = await this.validateResourceAccess(prisma, context, resourceId, resourceType)
|
||||
|
||||
if (!hasAccess) {
|
||||
// Логируем попытку несанкционированного доступа
|
||||
await CommercialDataAudit.logUnauthorizedAccess({
|
||||
...context,
|
||||
resourceId,
|
||||
resourceType,
|
||||
})
|
||||
}
|
||||
|
||||
return hasAccess
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 Создание тестов для классов
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/__tests__/supply-data-filter.test.ts
|
||||
describe('SupplyDataFilter', () => {
|
||||
describe('filterForFulfillment', () => {
|
||||
it('should hide product prices from fulfillment', () => {
|
||||
const order = createMockSupplyOrder()
|
||||
const context = createMockContext('FULFILLMENT')
|
||||
|
||||
const filtered = SupplyDataFilter.filterSupplyOrder(order, context)
|
||||
|
||||
expect(filtered.data.items[0].price).toBeNull()
|
||||
expect(filtered.data.productPrice).toBeNull()
|
||||
expect(filtered.removedFields).toContain('productPrice')
|
||||
})
|
||||
|
||||
it('should show recipe to fulfillment', () => {
|
||||
const order = createMockSupplyOrder()
|
||||
const context = createMockContext('FULFILLMENT')
|
||||
|
||||
const filtered = SupplyDataFilter.filterSupplyOrder(order, context)
|
||||
|
||||
expect(filtered.data.items[0].recipe).toBeDefined()
|
||||
expect(filtered.data.items[0].recipe.services).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Результаты фазы 2:
|
||||
|
||||
- ✅ Базовые классы реализованы
|
||||
- ✅ 100% покрытие тестами
|
||||
- ✅ Готовы к интеграции в резолверы
|
||||
|
||||
## 🔄 ФАЗА 3: ОБНОВЛЕНИЕ РЕЗОЛВЕРОВ (5-7 дней)
|
||||
|
||||
### Цель:
|
||||
|
||||
Интегрировать классы безопасности в существующие GraphQL резолверы с минимальным риском.
|
||||
|
||||
### Задачи:
|
||||
|
||||
#### 3.1 Создание обертки для безопасных резолверов
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/secure-resolver.ts
|
||||
export function createSecureResolver<TArgs, TResult>(
|
||||
resolver: (parent: any, args: TArgs, context: Context) => Promise<TResult>,
|
||||
options: {
|
||||
resourceType: string
|
||||
requiredRole?: OrganizationType[]
|
||||
auditAction: string
|
||||
},
|
||||
) {
|
||||
return async (parent: any, args: TArgs, context: Context): Promise<TResult> => {
|
||||
// Проверка аутентификации
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Authentication required')
|
||||
}
|
||||
|
||||
// Проверка роли если требуется
|
||||
if (options.requiredRole && !options.requiredRole.includes(context.user.organizationType)) {
|
||||
throw new GraphQLError('Insufficient permissions')
|
||||
}
|
||||
|
||||
// Логирование доступа
|
||||
if (FEATURE_FLAGS.SUPPLY_DATA_SECURITY.auditEnabled) {
|
||||
await CommercialDataAudit.logAccess({
|
||||
userId: context.user.id,
|
||||
organizationType: context.user.organizationType,
|
||||
action: options.auditAction,
|
||||
resourceType: options.resourceType,
|
||||
metadata: { args },
|
||||
})
|
||||
}
|
||||
|
||||
// Выполнение оригинального резолвера
|
||||
const result = await resolver(parent, args, context)
|
||||
|
||||
// Фильтрация результата если включена безопасность
|
||||
if (FEATURE_FLAGS.SUPPLY_DATA_SECURITY.enabled) {
|
||||
return filterResultByRole(result, context)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 Постепенное обновление резолверов
|
||||
|
||||
```typescript
|
||||
// src/graphql/resolvers/supply-orders/queries.ts
|
||||
export const supplyOrderQueries = {
|
||||
// Старый резолвер
|
||||
mySupplyOrders_OLD: async (parent, args, context) => {
|
||||
// Существующая логика
|
||||
},
|
||||
|
||||
// Новый безопасный резолвер
|
||||
mySupplyOrders: createSecureResolver(
|
||||
async (parent, args, context) => {
|
||||
// Получаем данные
|
||||
const orders = await prisma.supplyOrder.findMany({
|
||||
where: buildWhereClause(context.user, args),
|
||||
include: fullInclude,
|
||||
})
|
||||
|
||||
// Фильтруем если безопасность включена
|
||||
if (FEATURE_FLAGS.SUPPLY_DATA_SECURITY.enabled) {
|
||||
return orders.map((order) => SupplyDataFilter.filterSupplyOrder(order, context).data)
|
||||
}
|
||||
|
||||
return orders
|
||||
},
|
||||
{
|
||||
resourceType: 'SUPPLY_ORDER',
|
||||
auditAction: 'VIEW_SUPPLY_ORDERS',
|
||||
},
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 A/B тестирование с метриками
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/metrics.ts
|
||||
export class SecurityMetrics {
|
||||
static async compareResults(oldResult: any, newResult: any, context: SecurityContext) {
|
||||
const differences = this.findDifferences(oldResult, newResult)
|
||||
|
||||
if (differences.length > 0) {
|
||||
await this.logDifferences({
|
||||
userId: context.user.id,
|
||||
organizationType: context.user.organizationType,
|
||||
differences,
|
||||
timestamp: new Date(),
|
||||
})
|
||||
}
|
||||
|
||||
// Отправка метрик в мониторинг
|
||||
metrics.increment('security.filter.applied', {
|
||||
organizationType: context.user.organizationType,
|
||||
hasDifferences: differences.length > 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4 Поэтапная миграция резолверов
|
||||
|
||||
```typescript
|
||||
// План миграции резолверов
|
||||
const MIGRATION_PLAN = [
|
||||
// Неделя 1: Читающие запросы
|
||||
{ resolver: 'mySupplyOrders', risk: 'LOW', rollout: '10%' },
|
||||
{ resolver: 'supplyOrder', risk: 'LOW', rollout: '25%' },
|
||||
{ resolver: 'searchSupplies', risk: 'MEDIUM', rollout: '10%' },
|
||||
|
||||
// Неделя 2: Мутации
|
||||
{ resolver: 'createSupplyOrder', risk: 'HIGH', rollout: '5%' },
|
||||
{ resolver: 'updateSupplyOrderStatus', risk: 'HIGH', rollout: '5%' },
|
||||
|
||||
// Неделя 3: Полный rollout
|
||||
{ resolver: '*', risk: 'MEDIUM', rollout: '100%' },
|
||||
]
|
||||
```
|
||||
|
||||
### Результаты фазы 3:
|
||||
|
||||
- ✅ Резолверы обновлены с feature flags
|
||||
- ✅ A/B тестирование настроено
|
||||
- ✅ Метрики собираются для анализа
|
||||
|
||||
## 📊 ФАЗА 4: СИСТЕМА АУДИТА (2-3 дня)
|
||||
|
||||
### Цель:
|
||||
|
||||
Реализовать полноценную систему аудита доступа к коммерческим данным.
|
||||
|
||||
### Задачи:
|
||||
|
||||
#### 4.1 Применение миграции БД
|
||||
|
||||
```bash
|
||||
# Применяем подготовленную миграцию
|
||||
npx prisma migrate deploy
|
||||
|
||||
# Обновляем Prisma Client
|
||||
npx prisma generate
|
||||
```
|
||||
|
||||
#### 4.2 Реализация CommercialDataAudit
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/commercial-data-audit.ts
|
||||
export class CommercialDataAudit {
|
||||
private static readonly ALERT_THRESHOLDS = {
|
||||
VIEW_PRICE: { perHour: 100, perDay: 500 },
|
||||
VIEW_RECIPE: { perHour: 50, perDay: 200 },
|
||||
BULK_EXPORT: { perHour: 5, perDay: 20 },
|
||||
}
|
||||
|
||||
static async logAccess(params: AuditParams): Promise<void> {
|
||||
// Сохраняем в БД
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: params.userId,
|
||||
organizationType: params.organizationType,
|
||||
action: params.action,
|
||||
resourceType: params.resourceType,
|
||||
resourceId: params.resourceId,
|
||||
metadata: params.metadata || {},
|
||||
ipAddress: params.ipAddress,
|
||||
userAgent: params.userAgent,
|
||||
timestamp: new Date(),
|
||||
},
|
||||
})
|
||||
|
||||
// Проверяем на подозрительную активность
|
||||
await this.checkSuspiciousActivity(params)
|
||||
}
|
||||
|
||||
private static async checkSuspiciousActivity(params: AuditParams) {
|
||||
const threshold = this.ALERT_THRESHOLDS[params.action]
|
||||
if (!threshold) return
|
||||
|
||||
// Считаем активность за последний час
|
||||
const hourlyCount = await this.getActivityCount(
|
||||
params.userId,
|
||||
params.action,
|
||||
60 * 60 * 1000, // 1 час
|
||||
)
|
||||
|
||||
if (hourlyCount > threshold.perHour) {
|
||||
await this.sendAlert({
|
||||
type: 'EXCESSIVE_ACCESS',
|
||||
severity: 'HIGH',
|
||||
userId: params.userId,
|
||||
action: params.action,
|
||||
count: hourlyCount,
|
||||
threshold: threshold.perHour,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 Dashboard для мониторинга
|
||||
|
||||
```typescript
|
||||
// src/pages/admin/security-audit.tsx
|
||||
export function SecurityAuditDashboard() {
|
||||
const [alerts, setAlerts] = useState<SecurityAlert[]>([])
|
||||
const [metrics, setMetrics] = useState<SecurityMetrics>()
|
||||
|
||||
// Real-time подписка на алерты
|
||||
useEffect(() => {
|
||||
const subscription = subscribeToSecurityAlerts((alert) => {
|
||||
setAlerts(prev => [alert, ...prev])
|
||||
|
||||
// Показываем критичные алерты
|
||||
if (alert.severity === 'HIGH') {
|
||||
toast.error(`Security Alert: ${alert.message}`)
|
||||
}
|
||||
})
|
||||
|
||||
return () => subscription.unsubscribe()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="security-dashboard">
|
||||
<AlertsList alerts={alerts} />
|
||||
<AccessMetrics metrics={metrics} />
|
||||
<SuspiciousActivityLog />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Результаты фазы 4:
|
||||
|
||||
- ✅ Аудит логирует все обращения
|
||||
- ✅ Алерты работают в real-time
|
||||
- ✅ Dashboard для мониторинга
|
||||
|
||||
## ✅ ФАЗА 5: ТЕСТИРОВАНИЕ (3-4 дня)
|
||||
|
||||
### Цель:
|
||||
|
||||
Обеспечить полное покрытие тестами и проверить все сценарии безопасности.
|
||||
|
||||
### Задачи:
|
||||
|
||||
#### 5.1 Unit тесты для каждой роли
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/__tests__/role-based-filtering.test.ts
|
||||
describe('Role-based filtering', () => {
|
||||
const testCases = [
|
||||
{
|
||||
role: 'SELLER',
|
||||
canSee: ['productPrice', 'recipe', 'totalAmount'],
|
||||
cannotSee: [],
|
||||
},
|
||||
{
|
||||
role: 'WHOLESALE',
|
||||
canSee: ['productPrice', 'packagesCount'],
|
||||
cannotSee: ['recipe', 'fulfillmentServicePrice'],
|
||||
},
|
||||
{
|
||||
role: 'FULFILLMENT',
|
||||
canSee: ['recipe', 'fulfillmentServicePrice'],
|
||||
cannotSee: ['productPrice'],
|
||||
},
|
||||
{
|
||||
role: 'LOGIST',
|
||||
canSee: ['logisticsPrice', 'routes'],
|
||||
cannotSee: ['productPrice', 'recipe', 'items'],
|
||||
},
|
||||
]
|
||||
|
||||
testCases.forEach(({ role, canSee, cannotSee }) => {
|
||||
describe(`${role} role`, () => {
|
||||
canSee.forEach((field) => {
|
||||
it(`should see ${field}`, async () => {
|
||||
const result = await testQuery(role, SUPPLY_ORDER_QUERY)
|
||||
expect(result.data.supplyOrder[field]).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
cannotSee.forEach((field) => {
|
||||
it(`should NOT see ${field}`, async () => {
|
||||
const result = await testQuery(role, SUPPLY_ORDER_QUERY)
|
||||
expect(result.data.supplyOrder[field]).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### 5.2 Integration тесты
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/__tests__/integration.test.ts
|
||||
describe('Supply chain security integration', () => {
|
||||
it('should isolate data between competitors', async () => {
|
||||
// Создаем двух селлеров-конкурентов
|
||||
const seller1 = await createTestSeller()
|
||||
const seller2 = await createTestSeller()
|
||||
|
||||
// Seller1 создает поставку
|
||||
const supply1 = await createSupplyOrder(seller1, {
|
||||
productPrice: 1000,
|
||||
recipe: { services: ['Packing'] },
|
||||
})
|
||||
|
||||
// Seller2 пытается получить доступ
|
||||
const result = await querySupplyOrder(seller2, supply1.id)
|
||||
|
||||
expect(result.errors[0].message).toBe('Access denied')
|
||||
})
|
||||
|
||||
it('should allow partners to see limited data', async () => {
|
||||
const seller = await createTestSeller()
|
||||
const wholesale = await createTestWholesale()
|
||||
const fulfillment = await createTestFulfillment()
|
||||
|
||||
// Создаем партнерства
|
||||
await createPartnership(seller, wholesale)
|
||||
await createPartnership(seller, fulfillment)
|
||||
|
||||
// Создаем поставку
|
||||
const supply = await createSupplyOrder(seller, {
|
||||
partnerId: wholesale.id,
|
||||
fulfillmentCenterId: fulfillment.id,
|
||||
productPrice: 1000,
|
||||
recipe: { services: ['Packing'] },
|
||||
})
|
||||
|
||||
// Поставщик видит свою часть
|
||||
const wholesaleView = await querySupplyOrder(wholesale, supply.id)
|
||||
expect(wholesaleView.data.productPrice).toBe(1000)
|
||||
expect(wholesaleView.data.recipe).toBeNull()
|
||||
|
||||
// Фулфилмент видит свою часть
|
||||
const fulfillmentView = await querySupplyOrder(fulfillment, supply.id)
|
||||
expect(fulfillmentView.data.productPrice).toBeNull()
|
||||
expect(fulfillmentView.data.recipe).toBeDefined()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### 5.3 Performance тесты
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/__tests__/performance.test.ts
|
||||
describe('Security performance', () => {
|
||||
it('should not significantly impact query performance', async () => {
|
||||
const iterations = 100
|
||||
|
||||
// Тест без фильтрации
|
||||
const withoutSecurity = await measurePerformance(async () => {
|
||||
await queryWithoutSecurity(COMPLEX_SUPPLY_QUERY)
|
||||
}, iterations)
|
||||
|
||||
// Тест с фильтрацией
|
||||
const withSecurity = await measurePerformance(async () => {
|
||||
await queryWithSecurity(COMPLEX_SUPPLY_QUERY)
|
||||
}, iterations)
|
||||
|
||||
const overhead = (withSecurity.avg - withoutSecurity.avg) / withoutSecurity.avg
|
||||
|
||||
// Допустимый overhead - 15%
|
||||
expect(overhead).toBeLessThan(0.15)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Результаты фазы 5:
|
||||
|
||||
- ✅ Полное покрытие unit тестами
|
||||
- ✅ Integration тесты проверяют изоляцию
|
||||
- ✅ Performance overhead < 15%
|
||||
|
||||
## 🚀 ФАЗА 6: ОПТИМИЗАЦИЯ И ФИНАЛИЗАЦИЯ (2-3 дня)
|
||||
|
||||
### Цель:
|
||||
|
||||
Оптимизировать производительность и подготовить к production.
|
||||
|
||||
### Задачи:
|
||||
|
||||
#### 6.1 Кеширование фильтров
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/cache.ts
|
||||
export class SecurityCache {
|
||||
private static cache = new LRUCache<string, FilteredData>({
|
||||
max: 1000,
|
||||
ttl: 5 * 60 * 1000, // 5 минут
|
||||
})
|
||||
|
||||
static getCacheKey(resourceId: string, userId: string, organizationType: string): string {
|
||||
return `${resourceId}:${userId}:${organizationType}`
|
||||
}
|
||||
|
||||
static get(key: string): FilteredData | undefined {
|
||||
return this.cache.get(key)
|
||||
}
|
||||
|
||||
static set(key: string, data: FilteredData): void {
|
||||
this.cache.set(key, data)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.2 Batch фильтрация
|
||||
|
||||
```typescript
|
||||
// src/graphql/security/batch-filter.ts
|
||||
export class BatchFilter {
|
||||
static async filterSupplyOrders(
|
||||
orders: SupplyOrder[],
|
||||
context: SecurityContext,
|
||||
): Promise<FilteredData<SupplyOrder>[]> {
|
||||
// Группируем по типам доступа
|
||||
const grouped = this.groupByAccessLevel(orders, context)
|
||||
|
||||
// Применяем фильтры параллельно
|
||||
const filtered = await Promise.all([
|
||||
this.filterFullAccess(grouped.full, context),
|
||||
this.filterPartialAccess(grouped.partial, context),
|
||||
this.filterNoAccess(grouped.none, context),
|
||||
])
|
||||
|
||||
return filtered.flat()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.3 Документация для разработчиков
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* @example Использование безопасных резолверов
|
||||
*
|
||||
* // Для queries
|
||||
* export const mySecureQuery = createSecureResolver(
|
||||
* async (parent, args, context) => {
|
||||
* // Ваша логика
|
||||
* },
|
||||
* {
|
||||
* resourceType: 'SUPPLY_ORDER',
|
||||
* auditAction: 'VIEW_ORDERS'
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* // Для mutations
|
||||
* export const mySecureMutation = createSecureResolver(
|
||||
* async (parent, args, context) => {
|
||||
* // Ваша логика
|
||||
* },
|
||||
* {
|
||||
* resourceType: 'SUPPLY_ORDER',
|
||||
* requiredRole: ['SELLER', 'WHOLESALE'],
|
||||
* auditAction: 'CREATE_ORDER'
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
```
|
||||
|
||||
### Результаты фазы 6:
|
||||
|
||||
- ✅ Performance оптимизирован
|
||||
- ✅ Документация готова
|
||||
- ✅ Готово к production
|
||||
|
||||
## 🔍 МОНИТОРИНГ И МЕТРИКИ
|
||||
|
||||
### Ключевые метрики для отслеживания:
|
||||
|
||||
```typescript
|
||||
interface SecurityMetrics {
|
||||
// Performance метрики
|
||||
filteringOverhead: number // Процент замедления
|
||||
cacheHitRate: number // Эффективность кеша
|
||||
|
||||
// Security метрики
|
||||
unauthorizedAccessAttempts: number // Попытки несанкц. доступа
|
||||
dataLeaksPrevented: number // Предотвращенные утечки
|
||||
|
||||
// Business метрики
|
||||
affectedQueries: number // Количество затронутых запросов
|
||||
userComplaints: number // Жалобы пользователей
|
||||
}
|
||||
```
|
||||
|
||||
### Алерты:
|
||||
|
||||
```yaml
|
||||
alerts:
|
||||
- name: high_unauthorized_access
|
||||
condition: rate(unauthorized_access) > 10/min
|
||||
severity: critical
|
||||
|
||||
- name: performance_degradation
|
||||
condition: filtering_overhead > 25%
|
||||
severity: warning
|
||||
|
||||
- name: audit_log_failure
|
||||
condition: audit_write_errors > 0
|
||||
severity: critical
|
||||
```
|
||||
|
||||
## ✅ КОНТРОЛЬНЫЙ СПИСОК ГОТОВНОСТИ
|
||||
|
||||
### Перед каждой фазой:
|
||||
|
||||
- [ ] Feature flag настроен и протестирован
|
||||
- [ ] Rollback план готов
|
||||
- [ ] Метрики и логирование настроены
|
||||
- [ ] Команда проинформирована
|
||||
|
||||
### Перед production:
|
||||
|
||||
- [ ] Все тесты проходят (unit, integration, e2e)
|
||||
- [ ] Performance overhead < 15%
|
||||
- [ ] Security review пройден
|
||||
- [ ] Документация обновлена
|
||||
- [ ] Мониторинг настроен
|
||||
- [ ] Support команда обучена
|
||||
|
||||
### После deployment:
|
||||
|
||||
- [ ] Мониторинг метрик первые 24 часа
|
||||
- [ ] Анализ логов на ошибки
|
||||
- [ ] Feedback от пользователей
|
||||
- [ ] Performance отчет
|
||||
|
||||
## 🚨 ПЛАН ОТКАТА
|
||||
|
||||
### Быстрый откат (< 5 минут):
|
||||
|
||||
```bash
|
||||
# Отключение через environment
|
||||
ENABLE_SUPPLY_SECURITY=false
|
||||
ENABLE_SECURITY_AUDIT=false
|
||||
|
||||
# Перезапуск сервисов
|
||||
kubectl rollout restart deployment/api-server
|
||||
```
|
||||
|
||||
### Полный откат (< 30 минут):
|
||||
|
||||
```bash
|
||||
# Откат к предыдущей версии
|
||||
kubectl rollout undo deployment/api-server
|
||||
|
||||
# Откат миграции БД если нужно
|
||||
npx prisma migrate resolve --rolled-back
|
||||
```
|
||||
|
||||
## 📈 КРИТЕРИИ УСПЕХА
|
||||
|
||||
1. **Безопасность**: 0 утечек коммерческих данных
|
||||
2. **Performance**: Overhead < 15%
|
||||
3. **Стабильность**: 0 критических инцидентов
|
||||
4. **UX**: 0 жалоб на недоступность данных
|
||||
5. **Аудит**: 100% логирование критических операций
|
||||
|
||||
---
|
||||
|
||||
_План разработан с учетом минимизации рисков и постепенного внедрения_
|
||||
_Дата: 2025-08-22_
|
||||
_Estimated effort: 17-24 дня_
|
||||
_Risk level: MEDIUM с правильным подходом_
|
Reference in New Issue
Block a user