feat(auth): add row/size caps + streaming to export-user-data

- Add per-collection row cap (default 10k, env EXPORT_ROW_CAP) via Prisma
  take on all findMany calls
- Add total size cap (default 100MB, env EXPORT_SIZE_CAP_MB); throws
  PayloadTooLargeException (413) when exceeded
- Convert response to Node.js Readable stream piped via NestJS StreamableFile
  to avoid large in-memory buffers
- Export ExportUserDataResult interface (stream + truncated flag) from handler
- Update controller to set Content-Type/Content-Disposition headers and
  return StreamableFile
- Document EXPORT_ROW_CAP and EXPORT_SIZE_CAP_MB env vars in Swagger
- Extend tests: row-cap assertion (take arg), size-cap 413 path, stream assertions

Fixes GOO-223 (M-1 from GOO-200 audit).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-24 12:10:54 +07:00
parent b4bb05479e
commit fa3ba88f40
34 changed files with 1494 additions and 45 deletions

View File

@@ -1,6 +1,6 @@
import { Global, type MiddlewareConsumer, Module, type NestModule, RequestMethod } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { APP_FILTER } from '@nestjs/core';
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { PrometheusModule, makeCounterProvider } from '@willsoto/nestjs-prometheus';
import {
@@ -10,8 +10,11 @@ import {
CACHE_DEGRADATION_TOTAL,
} from './infrastructure/cache.service';
import { EventBusService } from './infrastructure/event-bus.service';
import { EVENT_BUS, RedisStreamsEventBus } from './infrastructure/event-bus';
import { OutboxRelay, OutboxService } from './infrastructure/outbox';
import { FieldEncryptionService } from './infrastructure/field-encryption.service';
import { GlobalExceptionFilter } from './infrastructure/filters/global-exception.filter';
import { DeprecationInterceptor, VersionInterceptor } from './infrastructure/interceptors';
import { LoggerService } from './infrastructure/logger.service';
import { CorrelationIdMiddleware } from './infrastructure/middleware/correlation-id.middleware';
import { CsrfMiddleware } from './infrastructure/middleware/csrf.middleware';
@@ -35,6 +38,10 @@ import { TypesenseClientService } from './infrastructure/typesense-client.servic
RedisService,
CacheService,
EventBusService,
// RFC-004 Phase 0 (GOO-172) — durable async messaging backbone.
{ provide: EVENT_BUS, useClass: RedisStreamsEventBus },
OutboxService,
OutboxRelay,
TypesenseClientService,
makeCounterProvider({
name: CACHE_HIT_TOTAL,
@@ -55,8 +62,18 @@ import { TypesenseClientService } from './infrastructure/typesense-client.servic
provide: APP_FILTER,
useClass: GlobalExceptionFilter,
},
// RFC-001 Phase 1 (GOO-170) — order matters: VersionInterceptor first
// populates req.apiVersion; DeprecationInterceptor reads from it.
{
provide: APP_INTERCEPTOR,
useClass: VersionInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: DeprecationInterceptor,
},
],
exports: [PrismaService, RedisService, CacheService, LoggerService, EventBusService, FieldEncryptionService, TypesenseClientService, PrometheusModule],
exports: [PrismaService, RedisService, CacheService, LoggerService, EventBusService, EVENT_BUS, OutboxService, FieldEncryptionService, TypesenseClientService, PrometheusModule],
})
export class SharedModule implements NestModule {
configure(consumer: MiddlewareConsumer): void {