
- 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>
193 lines
5.9 KiB
Markdown
193 lines
5.9 KiB
Markdown
# 🔐 ПЛАН БЕЗОПАСНОСТИ 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 операций
|
||
- Двухфакторная аутентификация для изменения ключей
|