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>
This commit is contained in:
192
docs/API_KEYS_SECURITY_PLAN.md
Normal file
192
docs/API_KEYS_SECURITY_PLAN.md
Normal file
@ -0,0 +1,192 @@
|
||||
# 🔐 ПЛАН БЕЗОПАСНОСТИ API КЛЮЧЕЙ
|
||||
|
||||
## ТЕКУЩИЕ ПРОБЛЕМЫ БЕЗОПАСНОСТИ
|
||||
|
||||
1. **API ключи хранятся В ОТКРЫТОМ ВИДЕ** в БД ❌
|
||||
2. **Нет истории изменений** ключей ❌
|
||||
3. **Нет аудита доступа** к ключам ❌
|
||||
4. **Ключи видны в GraphQL ответах** ❌
|
||||
|
||||
## РЕКОМЕНДУЕМАЯ АРХИТЕКТУРА
|
||||
|
||||
### 1. ШИФРОВАНИЕ КЛЮЧЕЙ
|
||||
|
||||
```typescript
|
||||
// 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. ОБНОВЛЕННАЯ СХЕМА БД
|
||||
|
||||
```prisma
|
||||
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. БЕЗОПАСНЫЕ РЕЗОЛВЕРЫ
|
||||
|
||||
```typescript
|
||||
// Сохранение ключа
|
||||
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
|
||||
|
||||
```typescript
|
||||
// Никогда не возвращать расшифрованные ключи
|
||||
type ApiKey {
|
||||
id: ID!
|
||||
marketplace: MarketplaceType!
|
||||
isActive: Boolean!
|
||||
lastUsedAt: DateTime
|
||||
createdAt: DateTime!
|
||||
# НЕ ВКЛЮЧАТЬ: apiKey, encryptedKey, encryptionIv, encryptionTag
|
||||
# Показывать только маскированную версию
|
||||
maskedKey: String! # "••••••••••1234"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ
|
||||
|
||||
```env
|
||||
# .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 операций
|
||||
- Двухфакторная аутентификация для изменения ключей
|
Reference in New Issue
Block a user