Files
sfera-new/docs/API_KEYS_SECURITY_PLAN.md
Veronika Smirnova b6935428ab docs: добавить планы по API ключам и отладке статистики
- API_KEYS_IMPLEMENTATION_PLAN.md - план реализации системы API ключей
- API_KEYS_SECURITY_PLAN.md - план безопасности API ключей
- API_KEYS_SIMPLE_PLAN.md - упрощенный план API ключей
- DEBUG_SELLER_STATISTICS.md - отладка статистики селлеров
- FIX_API_KEYS_SAVING.md - исправление сохранения API ключей

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-18 21:31:27 +03:00

5.9 KiB
Raw Blame History

🔐 ПЛАН БЕЗОПАСНОСТИ API КЛЮЧЕЙ

ТЕКУЩИЕ ПРОБЛЕМЫ БЕЗОПАСНОСТИ

  1. API ключи хранятся В ОТКРЫТОМ ВИДЕ в БД
  2. Нет истории изменений ключей
  3. Нет аудита доступа к ключам
  4. Ключи видны в GraphQL ответах

РЕКОМЕНДУЕМАЯ АРХИТЕКТУРА

1. ШИФРОВАНИЕ КЛЮЧЕЙ

// services/crypto-service.ts
import crypto from 'crypto'

export class CryptoService {
  private algorithm = 'aes-256-gcm'
  private secretKey = process.env.ENCRYPTION_KEY // 32 байта

  encrypt(text: string): { encrypted: string; iv: string; tag: string } {
    const iv = crypto.randomBytes(16)
    const cipher = crypto.createCipheriv(this.algorithm, this.secretKey, iv)

    let encrypted = cipher.update(text, 'utf8', 'hex')
    encrypted += cipher.final('hex')

    const tag = cipher.getAuthTag()

    return {
      encrypted,
      iv: iv.toString('hex'),
      tag: tag.toString('hex'),
    }
  }

  decrypt(encrypted: string, iv: string, tag: string): string {
    const decipher = crypto.createDecipheriv(this.algorithm, this.secretKey, Buffer.from(iv, 'hex'))

    decipher.setAuthTag(Buffer.from(tag, 'hex'))

    let decrypted = decipher.update(encrypted, 'hex', 'utf8')
    decrypted += decipher.final('utf8')

    return decrypted
  }
}

2. ОБНОВЛЕННАЯ СХЕМА БД

model ApiKey {
  id               String   @id @default(cuid())
  marketplace      MarketplaceType
  encryptedKey     String   // Зашифрованный ключ
  encryptionIv     String   // Вектор инициализации
  encryptionTag    String   // Тег аутентификации
  keyHash          String   // Хеш для быстрого сравнения
  clientId         String?
  isActive         Boolean  @default(true)
  lastUsedAt       DateTime?
  expiresAt        DateTime?
  createdAt        DateTime @default(now())
  updatedAt        DateTime @updatedAt
  organizationId   String
  organization     Organization @relation(fields: [organizationId], references: [id])

  // Аудит
  createdById      String
  createdBy        User     @relation("ApiKeyCreatedBy", fields: [createdById], references: [id])
  lastModifiedById String?
  lastModifiedBy   User?    @relation("ApiKeyModifiedBy", fields: [lastModifiedById], references: [id])

  // История изменений
  auditLogs        ApiKeyAuditLog[]

  @@unique([organizationId, marketplace])
  @@index([keyHash])
  @@map("api_keys")
}

model ApiKeyAuditLog {
  id          String   @id @default(cuid())
  action      ApiKeyAction // CREATED, UPDATED, VALIDATED, USED, DEACTIVATED
  performedAt DateTime @default(now())
  performedBy String
  user        User     @relation(fields: [performedBy], references: [id])
  apiKeyId    String
  apiKey      ApiKey   @relation(fields: [apiKeyId], references: [id])
  metadata    Json?    // Дополнительная информация

  @@index([apiKeyId])
  @@index([performedBy])
  @@map("api_key_audit_logs")
}

enum ApiKeyAction {
  CREATED
  UPDATED
  VALIDATED
  USED
  DEACTIVATED
  REACTIVATED
  DELETED
}

3. БЕЗОПАСНЫЕ РЕЗОЛВЕРЫ

// Сохранение ключа
const encrypted = cryptoService.encrypt(apiKey)
await prisma.apiKey.create({
  data: {
    organizationId: user.organization.id,
    marketplace,
    encryptedKey: encrypted.encrypted,
    encryptionIv: encrypted.iv,
    encryptionTag: encrypted.tag,
    keyHash: crypto.createHash('sha256').update(apiKey).digest('hex'),
    createdById: context.user.id,
    // Аудит
    auditLogs: {
      create: {
        action: 'CREATED',
        performedBy: context.user.id,
        metadata: { ip: context.ip, userAgent: context.userAgent },
      },
    },
  },
})

// Использование ключа
const apiKeyRecord = await prisma.apiKey.findUnique({ where: { id } })
const decryptedKey = cryptoService.decrypt(
  apiKeyRecord.encryptedKey,
  apiKeyRecord.encryptionIv,
  apiKeyRecord.encryptionTag,
)

// Логирование использования
await prisma.apiKeyAuditLog.create({
  data: {
    action: 'USED',
    apiKeyId: apiKeyRecord.id,
    performedBy: context.user.id,
    metadata: { purpose: 'statistics_fetch' },
  },
})

4. ЗАЩИТА В GRAPHQL

// Никогда не возвращать расшифрованные ключи
type ApiKey {
  id: ID!
  marketplace: MarketplaceType!
  isActive: Boolean!
  lastUsedAt: DateTime
  createdAt: DateTime!
  # НЕ ВКЛЮЧАТЬ: apiKey, encryptedKey, encryptionIv, encryptionTag
  # Показывать только маскированную версию
  maskedKey: String! # "••••••••••1234"
}

5. ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ

# .env.local
ENCRYPTION_KEY=your-32-byte-base64-encoded-key-here # Генерировать: openssl rand -base64 32

ПЛАН МИГРАЦИИ

  1. Создать CryptoService
  2. Обновить схему БД с новыми полями
  3. Миграция существующих ключей (зашифровать)
  4. Обновить резолверы для работы с шифрованием
  5. Добавить аудит всех операций
  6. Обновить UI для показа только маскированных ключей

ДОПОЛНИТЕЛЬНЫЕ МЕРЫ

  • Ротация ключей шифрования каждые 90 дней
  • Автоматическое удаление неиспользуемых ключей
  • Уведомления об аномальной активности
  • Rate limiting для API операций
  • Двухфакторная аутентификация для изменения ключей