A lot of changes

This commit is contained in:
2026-05-14 14:32:19 +02:00
parent d2b83524cf
commit 7215eebb5a
43 changed files with 2941 additions and 444 deletions
+95
View File
@@ -0,0 +1,95 @@
# Hardening: abuso de pre-signed uploads (Opción B)
## Contexto
Con la Opción B el backend genera URLs pre-firmadas (pre-signed) para que el cliente suba ficheros directamente a S3, y luego el backend crea/actualiza entidades en DB referenciando `pictureName/audioName`.
Esto mejora latencia y descarga la API, pero abre un vector de abuso: un usuario puede llamar muchas veces a `RequestPlayListPictureUpload` / `RequestTrackUploadUrls` y subir muchos objetos a S3 sin llegar a crear la entidad final (objetos “huérfanos”).
## Qué pasa hoy si el usuario spamea
- Se generan muchas URLs pre-firmadas (costo bajo pero no cero).
- El usuario puede subir muchos objetos a S3 sin relación con una entidad en DB.
- El impacto principal es:
- coste/uso de S3 (storage + requests)
- posible saturación del endpoint de generación de URLs
## Objetivo
Reducir abuso y coste, manteniendo:
- UX simple
- backend sin streams
- workflows asíncronos + MassTransit Outbox
## Mitigaciones recomendadas (pueden coexistir)
### 1) Rate limiting por usuario/IP (primera línea de defensa)
Aplicar limitación en el host/API (no en `Application`). Ejemplos:
- `N` requests/min por `UserId`
- opcional: `M` requests/min por IP
Ventajas:
- Corta abuso sin tocar DB/S3.
- No cambia dominio.
Nota: requiere acceso al proyecto host (controllers/minimal API) para aplicarlo.
### 2) “Upload intents” en DB (control y conciliación)
Persistir una intención de upload cuando se genera un pre-signed URL.
Modelo sugerido (mínimo):
- `UploadIntent`
- `Id`
- `UserId`
- `Bucket`
- `Key`
- `ContentType`
- `ExpiresAt`
- `Status`: `Issued | Consumed | Expired`
- `CreatedAt`
Reglas:
- Al pedir URL (Request*Upload*):
- insertar intent `Issued`
- imponer límite por usuario: p.ej. `MaxActiveIntentsPerUser = 5`
- si supera el límite: devolver `429 Too Many Requests` (o `Conflict`) según convención del API
- Al crear/actualizar entidad (Create*/Update* V2):
- validar que el `pictureName/audioName` recibido corresponde a un intent `Issued` del mismo `UserId` y no expirado
- marcar intent como `Consumed`
Ventajas:
- Previene spam con reglas duras y auditables.
- Permite cleanup de huérfanos con certeza.
Coste:
- entidad + migración + lógica de validación.
### 3) Limitar el scope del pre-signed URL (reducción de daño)
Aplicar restricciones conservadoras:
- Reducir TTL:
- `ExpiresInSeconds` más bajo (p.ej. 60–120s en vez de 600s)
- Validar/limitar:
- `ContentType` permitido
- extensiones permitidas (`FileType`)
- Usar keys temporales por usuario:
- `PlayLists/TempUploads/{userId}/{guid}.{ext}`
- `Tracks/TempUploads/{userId}/{guid}.{ext}`
Luego, en el workflow/worker:
- mover/copiar de `TempUploads/...` a la ruta final (`OriginalPictures/...` / `OriginalAudios/...`)
- borrar el objeto temporal
Ventajas:
- Cleanup masivo sencillo (por prefijo `TempUploads/{userId}`)
- Reduce el impacto de huérfanos.
## Orden de implementación sugerido
1) Rate limiting en API (rápido + mayor impacto)
2) Upload intents en DB (límite de activos + validación al crear/actualizar)
3) Reducir TTL y mover a rutas temporales (`TempUploads/...`) + cleanup
## Parámetros recomendados (config)
- `UploadUrlExpiresInSeconds` (default: 120)
- `MaxActiveUploadIntentsPerUser` (default: 5)
- `AllowedPictureExtensions` / `AllowedAudioExtensions`
- `AllowedContentTypes` (lista blanca)
## Notas
- Estas defensas complementan el Outbox: el Outbox garantiza entrega de eventos tras commit, pero no evita el abuso de generación de URLs o almacenamiento de objetos huérfanos.