Analise de Seguranca - Plataforma ProCases
Data: 2026-02-11 Versao: 2.0 Branch analisada: procases Classificacao: CONFIDENCIAL - Documento interno para desenvolvedores Autor: Equipe de Seguranca
AVISO: Este documento NAO deve ser publicado, vinculado ou referenciado em nenhuma documentacao publica do site. Trata-se de uma analise interna de seguranca destinada exclusivamente ao time de desenvolvimento.
Sumario Executivo
Esta analise de seguranca foi realizada com base no codigo-fonte real do projeto na branch procases. Foram identificados 10 problemas de seguranca (sendo 3 criticos), 2 dependencias mortas, 4+ blocos de codigo morto, 8+ itens documentados no CLAUDE.md que NAO existem no codigo, e 8 gargalos para producao e escala. Este documento detalha cada achado com referencias diretas aos arquivos, trechos de codigo, impacto real, e recomendacoes priorizadas.
Indice
- Infraestrutura Implementada
- Problemas de Seguranca Encontrados
- Dependencias Mortas
- Codigo Morto
- Itens no CLAUDE.md que NAO Existem no Codigo
- Gargalos para Producao e Escala
- Recomendacoes Prioritarias
- Checklist Pre-Producao
- Plano de Escalabilidade
- Apendices
1. Infraestrutura Implementada
Esta secao documenta todos os mecanismos de robustez, integridade e seguranca de dados que estao efetivamente implementados e funcionando no codigo-fonte.
1.1 Double-Entry Bookkeeping (Contabilidade de Partida Dupla)
Arquivo: src/application/services/transaction.service.ts
Toda operacao financeira da plataforma gera DOIS registros atomicos na tabela Transaction: um DEBIT e um CREDIT pareados via relatedTransactionId.
Metodos implementados:
createDoubleEntry(fromUserId, toUserId, amount, reason)- Transferencia entre usuariosdebit(userId, amount, reason)- Usuario → Sistema (abertura de caixa, upgrade, batalha)credit(userId, amount, reason)- Sistema → Usuario (deposito, venda de item)transfer(fromUserId, toUserId, amount, reason)- P2P
Garantias:
- Invariante:
SUM(CREDITS) - SUM(DEBITS) = 0no sistema inteiro - Nenhum dinheiro e criado ou destruido - apenas transferido
- Rastreabilidade completa: cada transacao aponta para sua contraparte
SYSTEM_USER_ID (BigInt(0))representa a casa/plataforma
Source of Truth: O saldo real de qualquer usuario e SEMPRE calculado por TransactionRepository.getUserBalance(userId) via GROUP BY em todas as transacoes. O campo user.balanceCents e apenas um cache atualizado apos cada transacao.
1.2 Distributed Locks (Redlock)
Arquivo: src/infrastructure/locks/lock.service.ts
Locks distribuidos baseados em Redis previnem race conditions em todas as operacoes financeiras criticas. Usa a biblioteca redlock v5.0.0-beta.2.
Locks implementados:
| Lock | TTL | Uso |
|---|---|---|
withUserBalanceLock(userId) | 10s | Toda operacao que altera saldo |
withTransactionLock() | 5s | Criacao atomica de double-entry |
withWithdrawalLock(inventoryItemId) | 30s | Previne saque duplicado do mesmo item |
withLock(resource, callback, ttl) | Customizavel | Lock generico |
Configuracao:
- Retry: 10 tentativas com 200ms delay + 200ms jitter
- Drift factor: 0.01
- Auto-extension: threshold de 500ms
Padrao de uso no codigo:
LockService.withUserBalanceLock(userId) → Prisma.$transaction() → double-entry → commit → releaseCada operacao financeira adquire o lock do usuario ANTES de iniciar a transacao PostgreSQL, garantindo que apenas uma operacao financeira por usuario executa por vez.
1.3 BIGINT para Valores Monetarios (Zero Imprecisao)
100% consistente em todas as tabelas do schema Prisma. Nenhum valor monetario usa Decimal, Float ou String.
Campos implementados:
User:balanceCents,totalDepositedCents,totalWithdrawnCents,totalWageredCentsTransaction:amountCentsDeposit:amountCentsWithdrawal:amountCentsItem:valueCents(USD),valueCentsBrl(BRL),originalPriceCentsCaseOpening:pricePaidCents,itemValueCents,profitCentsCase:priceCentsUpgrade:valueInvestedCents,balanceUsedCents,targetValueCentsBattle:totalValueCentsBattlePlayer:totalProfitCentsBattleSettlement:valueCentsRafflePrize:valueCents,valueCentsBrl
Beneficios: Zero erro de ponto flutuante, operacoes atomicas no banco, comparacoes simples com inteiros, compativel com JavaScript BigInt.
1.4 Audit Log com Hash Chaining (Blockchain-like)
Arquivo: src/infrastructure/audit/audit.service.ts
Sistema de auditoria imutavel com encadeamento de hashes SHA-256. Cada registro aponta para o hash do registro anterior, formando uma cadeia verificavel. Qualquer alteracao em um registro intermediario quebra a cadeia.
Campos por registro:
id(Snowflake ID),userId,action,entityType,entityIdmetadata(JSON),ipAddress,userAgentpreviousHash→ hash do registro anteriorcurrentHash→ SHA256(previousHash:logData)
Metodos implementados:
log(userId, action, entityType, entityId, metadata, ipAddress, userAgent)logUserAction(userId, ...)- Acoes de usuariologSystemAction(action, ...)- Acoes do sistemaverifyChainIntegrity(startId?, endId?)- Verifica cadeia completa ou parcial
Endpoint de verificacao: GET /api/audit/verify-chain retorna { valid: boolean, corruptedLogId?: bigint, logsVerified: number }.
Indexes: userId, action, (entityType, entityId), createdAt, (action, createdAt DESC), (entityType, entityId, createdAt DESC).
1.5 Snowflake IDs (64-bit Distributed)
Arquivo: src/infrastructure/snowflake/snowflake.service.ts
IDs de 64 bits gerados localmente, sem dependencia de auto-increment do banco.
Estrutura do ID:
- 41 bits: Timestamp (epoch customizado: 2024-01-01)
- 10 bits: Machine ID (suporta 1024 maquinas)
- 12 bits: Sequence (4096 IDs por milissegundo por maquina)
Protecoes:
- Clock drift: recusa gerar ID se relogio voltar
- Sequence overflow: espera proximo milissegundo se 4096 IDs esgotados
- Machine ID configuravel via
MACHINE_ID(env variable) - Batch generation:
generateMany(count)para criacao em lote
Beneficios: Ordenaveis por tempo (sem ORDER BY extra), menores que UUID (8 bytes vs 16 bytes), indexacao mais eficiente, sem colisao entre instancias.
1.6 Prisma Transactions com Timeout Configurado
Arquivo: src/infrastructure/database/prisma.service.ts
Configuracao:
maxWait: 10 segundos (tempo maximo na fila de conexoes)timeout: 30 segundos (tempo maximo de execucao da transacao)- Lifecycle:
onModuleInit→$connect,onModuleDestroy→$disconnect
Padrao no codigo: Todas as operacoes financeiras usam prisma.$transaction() com suporte a externalTx?: PrismaTransactionClient para composicao de transacoes.
1.7 PostgreSQL: pg_trgm + GIN Indexes para Busca Fuzzy
Migration: 20260202120000_add_performance_indexes
A extensao pg_trgm esta habilitada no banco com um index GIN na tabela de itens:
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_items_name_trgm ON items USING gin (name gin_trgm_ops);Permite busca fuzzy/parcial por nome de item com performance O(1) via index invertido. Nao requer Elasticsearch ou servico externo.
1.8 PostgreSQL: 90+ Indexes de Performance
A migration 20260202120000_add_performance_indexes cria 50+ indexes adicionais alem dos indexes declarados no Prisma schema, totalizando 90+ indexes.
Destaques:
| Tabela | Index | Tipo | Uso |
|---|---|---|---|
items | idx_items_name_trgm | GIN (pg_trgm) | Busca fuzzy por nome |
case_items | idx_case_items_case_hash_range | B-tree composto | Provably Fair roll lookup |
case_items | idx_case_items_rare | B-tree parcial (WHERE is_rare) | Sistema FLIP |
transactions | idx_transactions_user_type_status | B-tree composto | Calculo de saldo |
user_inventory | idx_inventory_available | B-tree parcial (WHERE AVAILABLE) | Inventario do usuario |
battles | idx_battles_public_waiting | B-tree parcial (WHERE WAITING) | Lobby de batalhas |
case_openings | idx_case_openings_recent | B-tree DESC | Live drops |
notifications | idx_notifications_unread_type | B-tree parcial (WHERE UNREAD) | Notificacoes nao lidas |
items | idx_items_rarity_value | B-tree composto | Sistema de upgrades |
deposits | idx_deposits_pending | B-tree parcial (WHERE PENDING) | Depositos pendentes |
Partial indexes (WHERE clause) reduzem o tamanho do index significativamente ao indexar apenas registros relevantes (ex: apenas items AVAILABLE, apenas batalhas WAITING, apenas notificacoes UNREAD).
1.9 Schema Prisma: 35 Enums + 17 Unique Constraints + 17 Cascades
Enums tipados (35): Previnem estados invalidos no banco. UserStatus, UserRole, TransactionType, TransactionStatus, PaymentMethod, DepositStatus, WithdrawalMethod, WithdrawalStatus, CaseStatus, RiskLevel, Rarity, WeaponCategory, ItemExterior, InventoryItemStatus, WaxpeerWithdrawalStatus, NotificationType, NotificationStatus, BattleMode, BattleType, BattleStatus, BattleSettlementType, SwapStatus, RaffleStatus, EventType, EventStatus, RewardType. Os modulos mais recentes adicionam CrashCycleStatus, CrashProfitMode, SkinsBackDepositStatus, CryptoDepositStatus, entre outros (total: 35).
Unique constraints (17): Garantem unicidade de dados criticos. User.steamId, User.referralCode, CaseCategory.name, CaseCategory.slug, Case.slug, Item.marketHashName, CaseItem.(caseId,itemId), Transaction.relatedTransactionId, Deposit.externalId, WaxpeerWithdrawal.waxpeerId, WaxpeerWithdrawal.projectId, Battle.joinCode, BattlePlayer.(battleId,userId), RaffleTicket.(raffleId,ticketNumber), EventLevel.(eventId,level), EventProgress.(eventId,userId), EventReward.(eventLevelId,userId).
Cascade deletes (17): Integridade referencial automatica. Ao deletar uma entidade pai, entidades filhas sao removidas automaticamente (Session→User, CaseItem→Case, BattlePlayer→Battle, etc).
1.10 Redis: Sessions com Binding de IP/UA
Arquivo: src/infrastructure/auth/session.service.ts
Sessions armazenadas em Redis com binding de IP e User-Agent para deteccao de session hijacking.
Dados por sessao: userId, steamId, ip, userAgent, deviceId, createdAt, lastActivity.
Protecoes:
- Revogacao instantanea (deletar do Redis)
- Deteccao de hijacking: IP ou UA diferente = sessao invalida
- Multiplos dispositivos: cada sessao e independente
- Logout de todos os dispositivos: deleta todas as sessions do usuario
- TTL automatico: 7 dias de inatividade
1.11 2FA (TOTP) para Operacoes Sensiveis
Arquivo: src/infrastructure/auth/two-factor.service.ts
Autenticacao de dois fatores baseada em TOTP (Time-based One-Time Password) usando speakeasy. Compativel com Google Authenticator, Authy e similares.
Onde e exigido:
- Saques acima do threshold configuravel (
WITHDRAWAL_2FA_THRESHOLD_CENTS)
Funcionalidades:
- Geracao de secret com QR code
- Verificacao de token com window de 1 (aceita codigo anterior/posterior)
- Ativacao/desativacao pelo usuario
1.12 Seguranca HTTP: Helmet + CORS + CSRF
Arquivo: src/main.ts
Helmet (headers de seguranca):
- Content-Security-Policy (CSP)
- HTTP Strict Transport Security (HSTS)
- X-XSS-Protection
- X-Content-Type-Options: nosniff
- Referrer-Policy
CORS (whitelist estrita):
- Apenas dominios autorizados (procases.example, admin.procases.example, localhost em dev)
credentials: truepara cookies- Metodos restritos: GET, POST, PUT, PATCH, DELETE
CSRF Protection: @fastify/csrf-protection com signed cookies.
1.13 Rate Limiting 3 Camadas (NestJS Throttler)
Arquivo: src/app.module.ts
| Camada | Janela | Limite |
|---|---|---|
short | 1 segundo | 10 requisicoes |
medium | 10 segundos | 50 requisicoes |
long | 1 minuto | 100 requisicoes |
Aplicado globalmente via APP_GUARD. Endpoints financeiros tem limites adicionais via @Throttle({ default: { limit: 5, ttl: 60000 } }).
1.14 31 Repositories com Clean Architecture
Pattern de repositorios com interfaces no dominio e implementacoes na infraestrutura.
Repositories implementados (principais): Transaction, User, Deposit, Withdrawal, Case, CaseCategory, CaseOpening, Item, UserInventory, Upgrade, Battle, BattleSettlement, Swap, Notification, Raffle, Event, EventShoot, WaxpeerWithdrawal. Os modulos mais recentes adicionam SkinsBackDeposit, CryptoDeposit, WithdrawalAutoApprovalConfig, UserWithdrawalLimits, alem dos repositorios de Mines e Crash, totalizando 31 implementacoes (30 interfaces em domain/repositories/).
Todos seguem o padrao: IXxxRepository (interface em domain/repositories/) → XxxRepository (implementacao em infrastructure/database/repositories/).
1.15 Validacao de Entrada Global
ValidationPipe global com:
whitelist: true- Remove campos nao declarados no DTOforbidNonWhitelisted: true- Erro se campo extra for enviadotransform: true- Conversao automatica de tipos
SanitizePipe customizado: Remove tags HTML de inputs string (prevencao XSS).
DTOs com class-validator: Todos os endpoints usam DTOs tipados com decorators @IsNotEmpty, @IsString, @Length, @IsInt, @Min, @Max, etc.
1.16 Provably Fair (HMAC-SHA256)
Arquivo: src/application/services/provably-fair.service.ts
Sistema criptografico de verificacao independente de resultados.
Algoritmo:
- Backend gera
serverSeed(256 bits) e envia o commitmentHMAC-SHA256(serverSeed)(HMAC com o serverSeed como chave, mensagem vazia) ao usuario - Usuario define
clientSeed+nonceauto-incrementado - Roll =
HMAC-SHA256(serverSeed, clientSeed:nonce)→substring(0,13)→parseInt(hex,16)→% 10000000 + 1 - Range: 1 a 10.000.000
- Overflow retry: se
parseInt > Number.MAX_SAFE_INTEGER, usanonce + 1000000
Verificavel: Apos o jogo, serverSeed e revelado. Qualquer pessoa pode recalcular HMAC-SHA256(serverSeed) e confirmar que bate com o commitment mostrado antes.
Resumo da Infraestrutura
| Componente | Status | Nota |
|---|---|---|
| Double-Entry Bookkeeping | Implementado | Todas as operacoes financeiras |
| Distributed Locks (Redlock) | Implementado | Saldo, transacoes, saques |
| BIGINT para dinheiro | Implementado | 100% consistente, zero imprecisao |
| Audit Log Hash Chaining | Implementado | Imutavel, verificavel |
| Snowflake IDs | Implementado | 64-bit, clock drift protection |
| Prisma Transactions | Implementado | 10s maxWait, 30s timeout |
| pg_trgm + GIN Index | Implementado | Busca fuzzy sem Elasticsearch |
| 90+ Indexes | Implementado | Partial indexes, compostos |
| 35 Enums + 17 Uniques | Implementado | Prevencao de estados invalidos |
| Sessions Redis + IP/UA | Implementado | Deteccao de hijacking |
| 2FA TOTP | Implementado | Saques acima do threshold |
| Helmet + CORS + CSRF | Implementado | Headers, whitelist, signed cookies |
| Rate Limiting 3 camadas | Implementado | short/medium/long |
| 31 Repositories | Implementado | Clean Architecture |
| Validacao + Sanitizacao | Implementado | ValidationPipe + SanitizePipe |
| Provably Fair | Implementado | HMAC-SHA256, range 1-10M |
2. Problemas de Seguranca Encontrados
2.1 [CRITICO] POST /raffle/:id/draw - So tem AuthGuard, qualquer usuario autenticado pode sortear
Arquivo: src/presentation/controllers/raffle.controller.ts (linhas 154-160)
Codigo atual:
@Post(':id/draw')
@UseGuards(AuthGuard)
@ApiOperation({ summary: 'Sortear vencedor (Admin)' })
@ApiResponse({ status: 200, description: 'Sorteio realizado' })
async drawRaffle(@Param('id') raffleId: string, @Body() dto: DrawRaffleDto) {
const result = await this.drawRaffleUseCase.execute(BigInt(raffleId), dto.clientSeed);
return this.formatRaffle(result);
}Problema: O endpoint usa apenas @UseGuards(AuthGuard). Qualquer usuario autenticado (mesmo um usuario comum recem-cadastrado) pode chamar este endpoint e sortear um raffle. O proprio comment da ApiOperation diz "Admin", mas o guard AdminGuard NAO esta aplicado.
Impacto: Um usuario mal-intencionado pode sortear qualquer raffle ativo a qualquer momento, potencialmente antes que todos os tickets sejam vendidos ou em momento estrategico que o beneficie. Isso compromete a integridade do sistema de sorteios inteiro.
Comparacao com AdminController: O AdminController (src/presentation/controllers/admin.controller.ts, linha 106) aplica AMBOS os guards no nivel do controller:
@UseGuards(AuthGuard, AdminGuard)
export class AdminController {Correcao necessaria: Adicionar AdminGuard ao endpoint:
@Post(':id/draw')
@UseGuards(AuthGuard, AdminGuard)
@ApiOperation({ summary: 'Sortear vencedor (Admin)' })Severidade: CRITICA - Permite que qualquer usuario execute acao administrativa.
2.2 [CRITICO] Endpoints de Audit sem AdminGuard - Qualquer usuario acessa logs de auditoria
Arquivo: src/presentation/controllers/audit.controller.ts
Codigo atual:
@ApiTags('Audit')
@Controller('audit')
@UseGuards(AuthGuard)
export class AuditController {
// ...
@Get('verify-chain')
@ApiOperation({ summary: 'Verify audit chain integrity (admin only)' })
async verifyChain(...) { ... }
@Get('entity/:type/:id')
@ApiOperation({ summary: 'Get entity audit logs (admin only)' })
async getEntityLogs(...) { ... }
}Problema: O controller inteiro usa apenas @UseGuards(AuthGuard). Existem DOIS endpoints que os proprios comments da ApiOperation indicam como "admin only", mas que NAO possuem AdminGuard:
GET /audit/verify-chain- Verifica integridade da chain de auditoria. Qualquer usuario autenticado pode executar esta operacao, que pode ser computacionalmente cara (itera sobre todos os logs de auditoria) e revelar informacoes sensiveis sobre a integridade do sistema.GET /audit/entity/:type/:id- Retorna logs de auditoria por entidade. Qualquer usuario autenticado pode ver logs de QUALQUER entidade do sistema, incluindo logs de transacoes financeiras de OUTROS usuarios, operacoes administrativas, mudancas de status de contas, etc.
Impacto: Vazamento massivo de informacoes. Um usuario pode:
- Ver todas as transacoes financeiras de outros usuarios
- Ver operacoes administrativas (bans, ajustes de saldo, etc.)
- Ver metadata sensivel armazenada nos logs de auditoria
- Consumir recursos do servidor com verificacoes de chain pesadas
- Identificar padroes internos de operacao da plataforma
Nota: O endpoint GET /audit/my-logs esta corretamente protegido pois filtra pelo userId do usuario autenticado. Os outros dois NAO filtram.
Correcao necessaria:
@Controller('audit')
@UseGuards(AuthGuard, AdminGuard)
export class AuditController {Ou, se GET /audit/my-logs deve continuar acessivel para usuarios comuns:
@Controller('audit')
@UseGuards(AuthGuard)
export class AuditController {
@Get('my-logs')
async getMyLogs(...) { ... } // OK - filtra pelo userId do usuario
@Get('verify-chain')
@UseGuards(AdminGuard) // Adicionar
async verifyChain(...) { ... }
@Get('entity/:type/:id')
@UseGuards(AdminGuard) // Adicionar
async getEntityLogs(...) { ... }
}Severidade: CRITICA - Vazamento de dados sensiveis de todos os usuarios.
2.3 [ALTO] POST /upgrade/:id/verify - Sem guard explicito, depende do guard global
Arquivo: src/presentation/controllers/upgrade.controller.ts (linhas 156-167)
Codigo atual:
@Post(':id/verify')
@ApiOperation({ summary: 'Verify upgrade provably fair' })
@ApiResponse({ status: 200, description: 'Verification result' })
async verifyUpgrade(@Param('id') id: string): Promise<VerifyUpgradeResponseDto> {
const result = await this.verifyUpgradeUseCase.execute(BigInt(id));
// ...
}Problema: Este endpoint NAO tem nenhum @UseGuards() no metodo NEM o controller inteiro usa guard no nivel de classe. O controller UpgradeController NAO tem @UseGuards() no nivel do controller. O endpoint depende exclusivamente do guard global (se existir).
Analise do controller:
@Public() @Get('targets')- Publico explicitamente. OK.@UseGuards(AuthGuard) @Post('calculate')- Guard explicito. OK.@UseGuards(AuthGuard) @Post('perform')- Guard explicito. OK.@UseGuards(AuthGuard) @Get('history')- Guard explicito. OK.@Post(':id/verify')- SEM guard, SEM@Public(). Ambiguo.
Impacto: Se houver um guard global APP_GUARD, o endpoint pode estar protegido por autenticacao. Mas sem @Public() explicito, a intencao nao esta clara. Se nao houver guard global, qualquer pessoa anonima pode verificar upgrades de qualquer usuario (o que pode nao ser um problema funcional, mas expoe informacoes como serverSeed, clientSeed, nonce e roll de QUALQUER upgrade ja feito).
Risco de exposicao de dados: O response inclui serverSeed revelado, que poderia ser usado para analise de padroes se combinado com dados de outros endpoints.
Correcao necessaria: Se a intencao e ser publico (para verificacao provably fair por qualquer pessoa):
@Public()
@Post(':id/verify')Se a intencao e ser restrito ao dono do upgrade:
@UseGuards(AuthGuard)
@Post(':id/verify')
async verifyUpgrade(@CurrentUser('id') userId: bigint, @Param('id') id: string) {
// Verificar que o upgrade pertence ao userId
}Severidade: ALTA - Ambiguidade de autorizacao em endpoint que expoe dados criptograficos.
2.4 [ALTO] GET /event/active - Sem guard nenhum, usa @CurrentUser opcional sem OptionalAuthGuard
Arquivo: src/presentation/controllers/event.controller.ts (linhas 22-26)
Codigo atual:
@ApiTags('Event')
@Controller('event')
export class EventController {
// ...
@Get('active')
@ApiOperation({ summary: 'Get all active events' })
async getActiveEvents(@CurrentUser('id') userId?: bigint) {
return this.getActiveEventsUseCase.execute(userId);
}Problema: O controller EventController NAO tem guard no nivel de classe. O endpoint GET /event/active NAO tem nenhum guard, NAO tem @Public(), e tenta usar @CurrentUser('id') como parametro opcional.
Questao tecnica: O decorator @CurrentUser depende de request.user estar populado, o que so acontece quando AuthGuard ou OptionalAuthGuard executa. Sem nenhum guard, request.user sera undefined, e userId sera sempre undefined.
Comparacao com raffle.controller.ts: O RaffleController resolve este problema corretamente no endpoint GET /raffle/:id:
@UseGuards(OptionalAuthGuard)
@Public()
@Get(':id')
async getRaffleDetails(@Param('id') raffleId: string, @CurrentUser('id') userId?: bigint) {Ele usa @UseGuards(OptionalAuthGuard) + @Public() para permitir acesso anonimo mas ainda assim popular request.user se houver sessao valida.
Impacto: O userId nunca sera populado no endpoint de eventos ativos. Se o use case getActiveEventsUseCase.execute(userId) personaliza a resposta baseada no usuario (ex: mostrando progresso individual), essa personalizacao nunca funcionara. O endpoint sempre retornara a versao "anonima" dos eventos.
Correcao necessaria:
@UseGuards(OptionalAuthGuard)
@Public()
@Get('active')
@ApiOperation({ summary: 'Get all active events' })
async getActiveEvents(@CurrentUser('id') userId?: bigint) {
return this.getActiveEventsUseCase.execute(userId);
}Severidade: ALTA - Funcionalidade quebrada silenciosamente + ambiguidade de autorizacao.
2.5 [ALTO] Decorator @CurrentUser Duplicado - Dois arquivos identicos com imports divergentes
Arquivos:
src/presentation/decorators/user.decorator.tssrc/presentation/decorators/current-user.decorator.ts
Conteudo de user.decorator.ts:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: string | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);Conteudo de current-user.decorator.ts:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: string | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
export const CurrentSession = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.sessionId;
});Controllers que importam de user.decorator.ts (arquivo ERRADO/duplicado):
raffle.controller.tsswap.controller.tsevent.controller.ts
Controllers que importam de current-user.decorator.ts (arquivo principal):
admin.controller.tsaudit.controller.tsauth.controller.tsbattle.controller.tscase-opening.controller.tsevent-shoot.controller.tsinventory.controller.tsnotification.controller.tspayment.controller.tsupgrade.controller.tsuser.controller.ts
Impacto: Atualmente ambos os decorators sao funcionalmente identicos (exceto que current-user.decorator.ts tambem exporta CurrentSession). O risco real e divergencia futura: se alguem alterar a logica do CurrentUser em um arquivo, o outro permanecera com a versao antiga. Isso pode causar bugs sutis e dificeis de diagnosticar onde alguns controllers obtem o usuario de um jeito e outros de outro.
Risco adicional: O arquivo user.decorator.ts NAO exporta CurrentSession. Se um dos controllers que importa de user.decorator.ts precisar do CurrentSession, o desenvolvedor pode criar OUTRA versao duplicada.
Correcao necessaria:
- Deletar
src/presentation/decorators/user.decorator.ts - Atualizar os imports em
raffle.controller.ts,swap.controller.tseevent.controller.tspara importar decurrent-user.decorator.ts
Severidade: ALTA - Risco de divergencia futura com impacto de seguranca.
2.6 [MEDIO] Referral System Inerte - Codigo existe mas nao faz nada
Arquivos afetados:
src/application/use-cases/auth/steam-callback.use-case.ts- GerareferralCodesrc/infrastructure/database/repositories/user.repository.ts- SalvareferredByIdsrc/application/use-cases/user/get-user-profile.use-case.ts- RetornareferralCodesrc/application/dto/user.dto.ts- DTO com camporeferralCode
Problema: O sistema de referral esta parcialmente implementado:
- Na criacao do usuario (Steam callback), um
referralCodeunico e gerado - Quando um novo usuario se cadastra usando o codigo de outro, o
referredByIde salvo no banco de dados - O
referralCodee retornado no perfil do usuario
Porem:
- NAO existe nenhum endpoint de referral (nenhum controller)
- NAO existe logica de recompensa (bonus para quem indicou, bonus para quem foi indicado)
- NAO existe contagem de referrals
- NAO existe nenhum use case de referral ativo
Impacto: Codigo morto que pode confundir desenvolvedores futuros. O usuario ve o referralCode no perfil, pode ate compartilhar o link, mas nada acontece quando alguem usa o codigo. Isso e uma experiencia frustrante para o usuario e pode gerar reclamacoes.
Correcao necessaria: Ou implementar o sistema completo de referrals (com bonus, contagem, dashboard) ou remover o codigo morto para evitar confusao.
Severidade: MEDIA - Funcionalidade parcial que pode confundir.
2.7 [ALTO] Items LOCKED Sem Cleanup - Items ficam presos permanentemente
Arquivo: src/application/services/waxpeer-withdrawal-tracker.service.ts
Cenario de falha:
- Usuario solicita withdrawal de um item de inventario
- O item e marcado como
LOCKEDno banco - A chamada para Waxpeer (
buyItemByNameToSend) falha ANTES de obter umwaxpeerId(ex: timeout, item indisponivel, erro de rede) - O item permanece com status
LOCKEDno inventario - O tracker so monitora withdrawals que TEM
waxpeerId:
const waxpeerIds = pendingWithdrawals
.filter((w) => w.waxpeerId)
.map((w) => w.waxpeerId as string);- O item fica permanentemente inacessivel para o usuario
Impacto: O usuario perde acesso ao item. Nao pode vender, trocar, sacar novamente ou usar em upgrade. O unico jeito de resolver e intervencao manual de um admin.
Nenhum cron job de cleanup existe: Verificado via busca por @Cron no projeto inteiro. O unico cron existente e o checkPendingWithdrawals do tracker, que NAO trata items LOCKED sem waxpeerId.
Correcao necessaria: Implementar cron job que a cada 15 minutos:
- Busca items com status
LOCKEDha mais de 1 hora - Verifica se existe
WaxpeerWithdrawalassociado comwaxpeerId - Se nao existe, reverte o item para
AVAILABLE - Notifica o usuario via WebSocket
- Loga a acao no AuditLog
Severidade: ALTA - Perda de acesso a itens do usuario.
2.8 [ALTO] Swaps Expirados Sem Cleanup - Items ficam IN_SWAP permanentemente
Arquivos:
src/domain/repositories/swap.repository.interface.ts(linha 34)src/infrastructure/database/repositories/swap.repository.ts(linha 197)
Codigo existente no repository:
// Interface
deleteExpiredSwaps(): Promise<number>;
// Implementacao
async deleteExpiredSwaps(): Promise<number> {
const result = await this.prisma.swap.updateMany({
where: {
status: SwapStatus.PENDING,
expiresAt: { lt: new Date() },
},
// ...
});
}Problema: O metodo deleteExpiredSwaps() existe e esta implementado, MAS nao e chamado por NENHUM cron job, scheduler, ou qualquer outro mecanismo automatico.
Busca no codigo: Nao ha nenhum @Cron chamando deleteExpiredSwaps. O unico @Cron no projeto inteiro e o do withdrawal tracker.
Impacto: Quando um usuario cria um swap e o outro usuario nao aceita dentro de 24 horas, os items do ofertante ficam com status IN_SWAP indefinidamente. O usuario nao pode:
- Vender os items
- Usar em upgrade
- Usar em outro swap
- Sacar os items
Correcao necessaria: Implementar cron job que executa a cada hora:
@Cron(CronExpression.EVERY_HOUR)
async cleanupExpiredSwaps() {
const expired = await this.swapRepository.deleteExpiredSwaps();
if (expired > 0) {
this.logger.log(`[SwapCleanup] Reverted ${expired} expired swaps`);
// Reverter items para AVAILABLE
// Notificar usuarios via WebSocket
}
}Severidade: ALTA - Items ficam permanentemente inacessiveis.
2.9 [MEDIO] Deposit Bonus 10% Hardcoded em TODO Deposito
Arquivo: src/application/services/deposit.service.ts (linhas 108-120)
Codigo atual:
const bonusPercentage = 10;
const bonusAmount = (deposit.amountCents * BigInt(bonusPercentage)) / BigInt(100);
const totalCredit = deposit.amountCents + bonusAmount;
await this.transactionService.credit(
deposit.userId,
totalCredit,
`Deposit confirmed #${depositId} (+${bonusPercentage}% bonus)`,
{
depositId: depositId.toString(),
method: deposit.method,
bonusPercentage,
bonusAmountCents: bonusAmount.toString(),
},
);Problema: O bonus de 10% e aplicado em TODOS os depositos de TODOS os usuarios, sem nenhuma verificacao de:
- Se e o primeiro deposito do usuario (
isFirstDeposit) - Se o usuario ja recebeu bonus antes
- Limite maximo de bonus por usuario
- Limite de depositos com bonus por periodo
Impacto financeiro: Um usuario pode fazer 100 depositos de R$100 e receber R$1.000 em bonus. Isso afeta diretamente a margem da casa e pode ser explorado sistematicamente.
Cenario de abuso: Um usuario faz depositos repetidos de valores altos, recebe 10% de bonus a cada vez, joga com risco minimo (ex: upgrades de 99% de chance), e saca o lucro. O bonus de 10% garante um edge positivo para o usuario.
Correcao necessaria:
- Verificar se e o primeiro deposito:
isFirstDeposit - Ou limitar bonus por periodo (ex: 1 bonus por mes)
- Ou usar tabela de bonus configuravel pelo admin
- Ou tornar o bonus condicional via configuracao (pode desativar)
Severidade: MEDIA - Impacto financeiro direto na casa.
2.10 [MEDIO] 2FA Secret Salvo no DB ANTES da Confirmacao
Arquivo: src/application/use-cases/payment/generate-2fa-secret.use-case.ts
Codigo atual:
async execute(userId: bigint) {
const user = await this.userRepository.findById(userId);
if (!user) throw new NotFoundException('User not found');
const { secret, otpauthUrl } = this.twoFactorService.generateSecret(user.username);
// Secret salvo ANTES do usuario confirmar com um token
await this.userRepository.update(userId, {
twoFactorSecret: secret,
});
const qrCode = await this.twoFactorService.generateQRCode(otpauthUrl);
return { secret, qrCode, otpauthUrl };
}Problema: O twoFactorSecret e salvo no banco de dados imediatamente quando o usuario chama POST /2fa/setup, ANTES de confirmar que configurou o app autenticador corretamente (via POST /2fa/enable).
Cenarios de problema:
- Usuario chama
/2fa/setupmultiplas vezes sem confirmar. Cada vez um novo secret e gerado e salvo, sobrescrevendo o anterior. - Se o usuario configurou o app com um secret anterior e depois chamou
/setupnovamente, o secret do app ficara fora de sincronia com o banco. - O campo
twoFactorSecretfica preenchido mastwoFactorEnabled = false, criando um estado inconsistente.
Risco adicional no verify2FA:
@Post('2fa/verify')
async verify2FA(@CurrentUser() user: User, @Body() dto: Enable2FADto) {
const isValid = user.twoFactorSecret
? await this.generate2FASecretUseCase['twoFactorService'].verifyToken(
user.twoFactorSecret,
dto.token,
)
: false;
return { valid: isValid };
}O endpoint verify2FA acessa this.generate2FASecretUseCase['twoFactorService'] usando bracket notation para acessar uma propriedade PRIVADA. Isso e um anti-pattern que quebra encapsulamento e pode falhar com minification ou obfuscation.
Correcao necessaria:
- NAO salvar o secret no DB durante o setup. Salvar em cache Redis com TTL de 10 minutos.
- Apenas salvar no DB apos confirmacao via
POST /2fa/enable. - Injetar
TwoFactorServicediretamente no controller ao inves de acessar via bracket notation.
Severidade: MEDIA - Inconsistencia de estado + anti-pattern de encapsulamento.
2.11 [MEDIO] Balance Source of Truth Inconsistente no Perfil do Usuario
Arquivo: src/application/use-cases/user/get-user-profile.use-case.ts
Codigo atual:
async execute(userId: bigint): Promise<UserProfileOutput> {
const user = await this.userRepository.findById(userId);
if (!user) throw new NotFoundException('User not found');
return {
// ...
balanceCents: user.balanceCents.toString(),
balance: this.moneyService.toReais(user.balanceCents).toFixed(2),
// ...
};
}Problema: O GET /user/profile retorna user.balanceCents, que e o CACHE do saldo, NAO a source of truth. Conforme documentado no CLAUDE.md:
O saldo do usuario SEMPRE vem das transacoes (TransactionRepository), NUNCA da coluna balanceCents da tabela User.
Porem o endpoint de perfil viola essa regra, usando user.balanceCents diretamente.
Impacto: Se por qualquer motivo o cache user.balanceCents ficar desincronizado com o saldo real das transacoes, o usuario vera um saldo diferente no perfil vs. no header (que atualiza via WebSocket com o saldo real).
Cenario de divergencia: Race condition durante transacao atomica que falha APOS atualizar user.balanceCents mas ANTES de comitar. O Prisma deveria fazer rollback, mas erros parciais podem ocorrer em cenarios de falha de conexao com o banco.
Correcao necessaria: Usar TransactionService.getUserBalance() no use case:
const realBalance = await this.transactionService.getUserBalance(userId);
return {
balanceCents: realBalance.toString(),
balance: this.moneyService.toReais(realBalance).toFixed(2),
};Severidade: MEDIA - Inconsistencia visual de saldo.
2.12 [MEDIO] WebSocket Waxpeer - Max 10 Reconnect sem Recovery
Arquivo: src/infrastructure/external-apis/waxpeer-websocket.service.ts (linhas 65-66, 172-187)
Codigo atual:
private reconnectAttempts = 0;
private readonly maxReconnectAttempts = 10;
private readonly reconnectDelay = 5000;
private scheduleReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
this.logger.error('[WaxpeerWS] Max reconnection attempts reached');
return; // Para DEFINITIVAMENTE. Sem recovery.
}
this.reconnectAttempts++;
const delay = this.reconnectDelay * this.reconnectAttempts;
// ...
}Problema: Apos 10 tentativas de reconnect (com backoff linear: 5s, 10s, 15s, ..., 50s = total ~4.5 minutos), o WebSocket do Waxpeer PARA completamente. Nao ha:
- Mecanismo de recovery apos cooldown
- Alerta para equipe operacional
- Health check periodico que tente reconectar
- Reset do contador apos periodo de inatividade
Impacto: O polling via @Cron(EVERY_30_SECONDS) continua como fallback, mas:
- Updates de status de withdrawal tem ate 30s de delay
- Se o Waxpeer WebSocket ficou fora por 5 minutos, todos os updates em tempo real param permanentemente ate restart manual da aplicacao
- Nenhum alerta e gerado para o time operacional
Correcao necessaria:
- Implementar recovery com cooldown (ex: a cada 30 minutos, reseta o contador e tenta novamente)
- Emitir alerta para equipe quando max tentativas for atingido
- Implementar health check periodico via
@Cronque verifica se o WebSocket esta conectado e tenta reconectar se necessario - Considerar reducao do polling interval para 10s quando WebSocket desconectado
Severidade: MEDIA - Degradacao silenciosa do tracking em tempo real.
2.13 [MEDIO] Tracker Trunca em 100 IDs por Ciclo
Arquivo: src/application/services/waxpeer-withdrawal-tracker.service.ts (linha 43) Arquivo: src/infrastructure/database/repositories/waxpeer-withdrawal.repository.ts (linha 94)
Codigo atual:
// Tracker
const pendingWithdrawals = await this.waxpeerWithdrawalRepository.findPending(100);
// Repository
async findPending(limit: number = 100): Promise<WaxpeerWithdrawal[]> {
return this.prisma.waxpeerWithdrawal.findMany({
where: {
status: { in: ['PENDING', 'WAITING_SELLER', 'TRADE_SENT'] },
},
take: limit,
// ...
});
}Problema: O tracker busca no maximo 100 withdrawals pendentes por ciclo de 30 segundos. Se houver mais de 100, os excedentes ficam sem monitoramento ate que os primeiros 100 sejam processados.
Analise de escala:
| Pendentes | Ciclos necessarios | Tempo total para cobrir todos |
|---|---|---|
| 50 | 1 | 30s |
| 100 | 1 | 30s |
| 200 | 2 | 60s (1 minuto) |
| 500 | 5 | 150s (2.5 minutos) |
| 1000 | 10 | 300s (5 minutos) |
Impacto: Em periodos de pico, usuarios com withdrawals mais recentes podem esperar varios minutos para o status ser atualizado. Nao ha priorizacao por idade ou valor.
Correcao necessaria:
- Aumentar batch size para 500 (Waxpeer suporta consulta de ate 100 IDs por chamada, mas o tracker pode fazer multiplas chamadas por ciclo)
- Ou implementar paginacao com cursor: se retornou 100, busca mais 100
- Priorizar por
createdAt ASCpara processar os mais antigos primeiro
Severidade: MEDIA - Delay no tracking em periodos de pico.
3. Dependencias Mortas
3.1 bullmq v5.4.0 - Instalado, ZERO uso no codigo
Arquivo: package.json (linha 57)
"bullmq": "^5.4.0",Verificacao: Busca por bullmq, BullModule, @InjectQueue, @Processor em todo o projeto retornou ZERO resultados em arquivos .ts.
Impacto:
- ~1.2MB de dependencia instalada sem uso
- Potencial confusao para desenvolvedores que podem pensar que filas ja estao implementadas
- Vulnerabilidades em dependencias nao utilizadas aumentam a superficie de ataque
Recomendacao: Remover do package.json imediatamente. Quando for necessario implementar filas (ex: para battle execution), reinstalar e configurar corretamente.
3.2 kafkajs v2.2.4 - Instalado, ZERO uso no codigo
Arquivo: package.json (linha 63)
"kafkajs": "^2.2.4",Verificacao: Busca por kafkajs em arquivos .ts retornou ZERO resultados. O CLAUDE.md menciona "Pub/Sub: Redis Pub/Sub (nao Kafka)" como decisao arquitetural explicita.
Impacto: Similar ao bullmq - dependencia morta que aumenta peso e superficie de ataque sem nenhum beneficio.
Nota: O CLAUDE.md lista uma estrutura de pastas com src/infrastructure/messaging/ contendo kafka.module.ts, kafka.service.ts, etc. Esses arquivos NAO existem no codigo real. Sao vestigios de um plano que nunca foi implementado.
Recomendacao: Remover do package.json.
4. Codigo Morto
4.1 emitWithdrawalProcessing() - Definido mas nunca chamado
Arquivo: src/infrastructure/websocket/websocket.gateway.ts (linha 488)
emitWithdrawalProcessing(
userId: bigint,
data: {
inventoryItemId: string;
waxpeerId?: string;
sendUntil?: number;
// ...
},
) {
// ...
}Verificacao: O metodo esta definido com assinatura completa e implementacao, mas NAO e chamado por nenhum outro arquivo no projeto. O tracker pula diretamente de PENDING para WAITING_SELLER ou TRADE_SENT, sem emitir o evento intermediario "processing".
Impacto: Pode dar a falsa impressao de que existe um estado "processing" no fluxo de withdrawal, quando na verdade nao ha.
Recomendacao: Remover o metodo. Se necessario no futuro, reimplementar com os estados corretos.
4.2 checkTradeStatusByProjectId() - Implementado mas nunca usado
Arquivo: src/infrastructure/external-apis/waxpeer.service.ts (linha 360)
async checkTradeStatusByProjectId(projectIds: string[]): Promise<WaxpeerCheckTradesResult> {
try {
if (projectIds.length === 0) {
return { success: true, trades: [] };
}
// ...
}
}Verificacao: O tracker usa checkTradeStatus() com waxpeerIds, nunca checkTradeStatusByProjectId().
Impacto: Similar ao anterior - confusao sobre qual metodo e o correto para verificar status.
Recomendacao: Remover ou marcar com @deprecated.
4.3 Arquivo user.decorator.ts - Duplicata desnecessaria
Arquivo: src/presentation/decorators/user.decorator.ts
Conforme detalhado na secao 2.5, este arquivo e uma copia identica do current-user.decorator.ts (sem o CurrentSession).
Recomendacao: Deletar o arquivo e atualizar imports.
4.4 Referral System - Codigo parcial sem funcionalidade
Conforme detalhado na secao 2.6, o sistema de referrals gera codigos e salva relacoes, mas nao oferece nenhum beneficio ou funcionalidade ao usuario.
Recomendacao: Implementar completamente ou remover.
5. Itens no CLAUDE.md que NAO Existem no Codigo
A seguinte tabela lista itens documentados no CLAUDE.md como parte da arquitetura do projeto, mas que NAO existem em nenhum arquivo .ts do codigo:
| Item no CLAUDE.md | Categoria | Existe? | Observacao |
|---|---|---|---|
ip-blacklist.service.ts | Seguranca | NAO | Nenhum arquivo com esse nome ou funcionalidade similar |
rate-limiter.service.ts | Seguranca | NAO | Nenhum service de rate limiting customizado |
rate-limit.middleware.ts | Seguranca | NAO | Nenhum middleware de rate limiting |
security-scan-detector.middleware.ts | Seguranca | NAO | Nenhuma deteccao de scan |
turnstile.service.ts (Cloudflare CAPTCHA) | Seguranca | NAO | Nenhuma integracao com Cloudflare Turnstile |
circuit-breaker.ts | Resiliencia | NAO | Nenhum circuit breaker pattern |
| Leaderboard/ranking | Funcionalidade | NAO | Nenhum endpoint ou use case de leaderboard |
| Risk scoring/fraud detection | Seguranca | NAO | Nenhum scoring de risco implementado |
| Promo code system | Funcionalidade | NAO | Nenhum sistema de codigos promocionais |
kafka.module.ts, kafka.service.ts, etc. | Infraestrutura | NAO | Kafka removido em favor de Redis Pub/Sub |
brute-force.service.ts | Seguranca | NAO VERIFICADO | Nao encontrado na busca |
prometheus/, grafana/ configs ativas | Monitoramento | PARCIAL | Configs Docker existem mas integracao no codigo e minima |
Impacto: O CLAUDE.md descreve uma arquitetura significativamente mais robusta do que o que esta realmente implementado. Isso pode:
- Dar uma falsa sensacao de seguranca
- Confundir novos desenvolvedores que esperam encontrar essas implementacoes
- Causar problemas em auditorias de seguranca se o documento for apresentado como reflexo do estado real
Recomendacao: Atualizar o CLAUDE.md para refletir o estado REAL do codigo. Mover itens nao implementados para uma secao "Planejado / TODO" separada.
6. Gargalos para Producao e Escala
6.1 Single Cron Job para TODOS os Withdrawals
Problema: O projeto inteiro possui apenas UM cron job ativo:
// waxpeer-withdrawal-tracker.service.ts
@Cron(CronExpression.EVERY_30_SECONDS)
async checkPendingWithdrawals() { ... }Este unico cron e responsavel por monitorar TODOS os withdrawals pendentes. Com batch de 100 e ciclo de 30s, a capacidade maxima e ~200 withdrawals/minuto.
Nenhum cron para:
- Cleanup de items LOCKED
- Cleanup de swaps expirados
- Cleanup de sessions expiradas (depende de Redis TTL apenas)
- Cleanup de deposits expirados/abandonados
- Sincronizacao de saldo cache vs. saldo real
- Health check de servicos externos
Impacto: Todo cleanup depende de acoes manuais ou Redis TTL. Dados inconsistentes acumulam indefinidamente no PostgreSQL.
6.2 Battle Execution Bloqueante - Roda inline no join
Arquivo: src/application/use-cases/battle/execute-battle.use-case.ts
Problema: A execucao da batalha e chamada diretamente no JoinBattleUseCase quando o ultimo jogador entra. O metodo execute() e assincrono mas bloqueia a thread do event loop por TODA a duracao da batalha:
// join-battle.use-case.ts (simplificado)
if (allSlotsFilled) {
await this.executeBattleUseCase.execute(battleId);
}O execute() contem:
await this.delay(3000)- Delay inicial de 3 segundos- Loop por cada round (ate 10 rounds):
- Para cada jogador, calcula roll, verifica flip
- Se FLIP: delay adicional + segunda roleta
- Emite WebSocket events
await this.delay(...)entre rounds
- Settlement de items
- Distribuicao justa
Tempo estimado:
- 10 rounds sem FLIP: ~85 segundos (3s inicio + 10 * ~8s + settlement)
- 10 rounds com FLIPs: ~120+ segundos
Impacto: Durante esses ~85-120 segundos:
- A request HTTP do
joinfica pendente (pode causar timeout) - A thread do Node.js esta ocupada com delays e calculos
- Outros requests podem ficar lentos
- Se o servidor restartar, a batalha e perdida
Correcao necessaria: Mover a execucao de batalhas para um worker BullMQ (a dependencia ja esta instalada, so nao esta configurada). O join deve apenas enfileirar a execucao e retornar imediatamente.
6.3 Redis Single Instance - Single Point of Failure
Problema: Todas as operacoes criticas dependem de uma UNICA instancia Redis:
- Sessions de autenticacao
- Distributed locks (Redlock)
- Cache de dados (Waxpeer snapshot, exchange rates)
- Pub/Sub para WebSocket (Redis Adapter)
- Battle state (estado intermediario de batalhas)
Se o Redis reiniciar:
- TODOS os usuarios sao deslogados instantaneamente
- Todas as batalhas em andamento sao perdidas
- Locks sao liberados prematuramente (race conditions)
- WebSocket desconecta todos os clientes
Nota sobre Redlock: O Redlock esta configurado com UM unico client Redis:
this.redlock = new Redlock([client], { ... });O algoritmo Redlock foi projetado para funcionar com multiplas instancias independentes (minimo 3 para tolerancia a falhas). Com uma unica instancia, o Redlock nao oferece nenhuma vantagem sobre um simples SET NX com TTL.
Recomendacao: Redis Sentinel com 3 nodes (1 master + 2 replicas).
6.4 PostgreSQL sem Particionamento
Problema: Tabelas de alto volume crescem indefinidamente:
- Transaction: 2 registros por operacao financeira. Com 1000 usuarios ativos fazendo 10 operacoes/dia = 20.000 registros/dia = 600.000/mes
- CaseOpening: 1 registro por abertura. Alto volume em picos.
- BattleRound: Multiplos registros por batalha (jogadores * rounds)
- AuditLog: 1+ registro por operacao auditada
Impacto no calculo de saldo: O TransactionRepository.getUserBalance() executa um GROUP BY em TODAS as transacoes do usuario:
const debitTransactions = await tx.transaction.groupBy({
by: ['type'],
where: { userId: debitUserId, status: 'COMPLETED' },
_sum: { amountCents: true },
});Com 100.000 transacoes por usuario (usuario ativo por 1 ano), este GROUP BY pode levar varios segundos, e isso executa DENTRO de uma transacao atomica com lock distribuido.
Recomendacao:
- Particionamento por data (mensal) para Transaction e CaseOpening
- Read replicas para queries de leitura pesada
- Ou: atualizacao incremental do cache de saldo (
decrement/increment) ao inves de recalculo completo
6.5 BullMQ Instalado mas NAO Configurado
Problema: bullmq v5.4.0 esta no package.json mas:
- Nenhum
BullModule.forRoot()ouBullModule.registerQueue()no AppModule - Nenhum
@Processor()declarado - Nenhum
@InjectQueue()usado - Nenhuma queue configurada
- Nenhum worker rodando
Batalhas DEVERIAM usar BullMQ ao inves de execucao sincrona inline (veja 6.2). Case openings poderiam beneficiar de processamento em fila em alta carga.
Recomendacao: Configurar BullMQ com:
- Queue
battle-executionpara batalhas - Dead Letter Queue para jobs falhados
- Dashboard de monitoramento (Bull Board)
6.6 ZERO Cleanup Jobs
Problema: Nenhum job de cleanup esta implementado para:
| Recurso | Problema sem cleanup | Impacto |
|---|---|---|
| Sessions Redis | TTL do Redis resolve | Baixo |
| Deposits expirados/abandonados | Ficam PENDING no DB indefinidamente | Baixo |
| Swaps expirados | Items presos em IN_SWAP | ALTO |
| Items LOCKED sem withdrawal | Items inacessiveis | ALTO |
| Battle states no Redis | Ocupam memoria | Baixo |
| Items orfaos no inventario | Estado inconsistente | Medio |
| AuditLog antigos | Tabela cresce infinitamente | Medio |
| Transactions antigas | Tabela cresce infinitamente | Medio |
Recomendacao: Implementar CleanupService com multiplos @Cron jobs para cada tipo de recurso.
6.7 Live Drops - Sem Cache Intermediario
Arquivo: src/presentation/controllers/live-drops.controller.ts
Problema: Cada request para GET /live-drops/recent executa queries no banco de dados. Considerando que o endpoint e publico e pode ser chamado por TODOS os visitantes do site, a carga pode ser significativa.
Recomendacao:
- Cache Redis com TTL de 10 segundos para live drops
- Ou materializar em Redis Sorted Set com TTL de 24h
- Cada use case (OpenCase, Upgrade, Swap) insere na lista de live drops apos completar
6.8 Waxpeer Snapshot Cache sem Circuit Breaker
Problema: O WaxpeerSnapshotService serve items publicos de um cache. Se a API do Waxpeer ficar indisponivel:
- O cache eventualmente expira
- Sem circuit breaker, o sistema continua tentando chamar a API
- Cada tentativa gera timeout + retry
- Acumula threads bloqueadas esperando timeout
Nao ha nenhum circuit-breaker.ts implementado no codigo (apesar de estar listado no CLAUDE.md).
Recomendacao: Implementar circuit breaker pattern para API do Waxpeer:
- Estado CLOSED: funciona normalmente
- Estado OPEN: retorna cache antigo sem tentar chamar API
- Estado HALF-OPEN: tenta uma chamada periodicamente para verificar recovery
7. Recomendacoes Prioritarias
7.1 Criticas - Fazer ANTES de ir para producao
| # | Acao | Arquivo(s) | Esforco | Descricao |
|---|---|---|---|---|
| 1 | Adicionar AdminGuard ao POST /raffle/:id/draw | raffle.controller.ts | 1 linha | Qualquer usuario pode sortear raffle |
| 2 | Adicionar AdminGuard aos endpoints de audit | audit.controller.ts | 1-3 linhas | Qualquer usuario acessa logs de auditoria de qualquer entidade |
| 3 | Implementar cron de cleanup para items LOCKED >1h sem withdrawal | Novo service | ~50 linhas | Items ficam permanentemente inacessiveis |
| 4 | Implementar cron de cleanup para swaps expirados | Novo cron chamando deleteExpiredSwaps() existente | ~30 linhas | Items ficam em IN_SWAP permanentemente |
| 5 | Remover dependencias mortas (bullmq, kafkajs) | package.json | 2 linhas | Dependencias sem uso, superficie de ataque |
| 6 | Mover battle execution para BullMQ worker | Varios | ~200 linhas | Bloqueia thread por ate 2 minutos |
| 7 | Verificar logica do bonus 10% de deposito | deposit.service.ts | ~20 linhas | Bonus em TODOS os depositos, exploravel |
7.2 Importantes - Fazer logo apos lancamento
| # | Acao | Arquivo(s) | Esforco | Descricao |
|---|---|---|---|---|
| 8 | Unificar decorator @CurrentUser (deletar user.decorator.ts) | 4 arquivos | ~10 linhas | Duplicata com risco de divergencia |
| 9 | Remover dead code (emitWithdrawalProcessing, checkTradeStatusByProjectId) | 2 arquivos | ~20 linhas | Codigo confuso e nao utilizado |
| 10 | Implementar referral system ou remover codigo | Varios | 200+ linhas | Funcionalidade parcial inutil |
| 11 | Corrigir GET /event/active com OptionalAuthGuard | event.controller.ts | 2 linhas | userId nunca populado |
| 12 | Explicitar guard no POST /upgrade/:id/verify | upgrade.controller.ts | 1 linha | Ambiguidade de autorizacao |
| 13 | Adicionar health check para Waxpeer WebSocket | waxpeer-websocket.service.ts | ~40 linhas | Degradacao silenciosa |
| 14 | Aumentar batch de withdrawals para 500 | tracker.service.ts | 1 linha | Delay em periodos de pico |
| 15 | Redis Sentinel para HA | Infra + config | Alto | SPOF critico |
| 16 | Nao salvar 2FA secret antes da confirmacao | generate-2fa-secret.use-case.ts | ~30 linhas | Estado inconsistente |
| 17 | Usar TransactionService.getUserBalance() no perfil | get-user-profile.use-case.ts | ~5 linhas | Source of truth inconsistente |
| 18 | Corrigir acesso bracket notation em verify2FA | payment.controller.ts | ~10 linhas | Anti-pattern de encapsulamento |
7.3 Desejaveis - Melhorias continuas
| # | Acao | Arquivo(s) | Esforco | Descricao |
|---|---|---|---|---|
| 19 | Cache de live drops em Redis (TTL 10s) | Novo service | ~50 linhas | Reduz queries ao banco |
| 20 | Particionamento de tabelas grandes | Migrations | Alto | Performance a longo prazo |
| 21 | Circuit breaker para APIs externas | Novo service | ~100 linhas | Resiliencia |
| 22 | Implementar IP blacklist, rate limiting, Turnstile | Varios | Alto | Prometidos no CLAUDE.md |
| 23 | Dead letter queue no BullMQ | Config | ~30 linhas | Jobs falhados nao sao perdidos |
| 24 | Metricas de latencia por endpoint | Middleware | ~50 linhas | Visibilidade de performance |
| 25 | Read replicas PostgreSQL | Infra | Alto | Distribui carga de leitura |
| 26 | Atualizar CLAUDE.md para refletir estado real | Doc | Medio | Elimina falsa sensacao de seguranca |
| 27 | Substituir console.log por Logger em TransactionService | transaction.service.ts | ~20 linhas | Dados financeiros em logs |
| 28 | Atualizacao incremental de saldo cache | transaction.service.ts | ~20 linhas | Reduz tempo de lock |
| 29 | WebSocket compression | Config | ~5 linhas | Economia de bandwidth |
| 30 | Recovery automatico para Waxpeer WebSocket | waxpeer-websocket.service.ts | ~30 linhas | Auto-healing |
8. Checklist Pre-Producao
8.1 Seguranca - Guards e Autorizacao
- [ ]
POST /raffle/:id/drawtem AdminGuard - [ ]
GET /audit/verify-chaintem AdminGuard - [ ]
GET /audit/entity/:type/:idtem AdminGuard - [ ]
POST /upgrade/:id/verifytem guard explicito (@Public ou AuthGuard) - [ ]
GET /event/activetem OptionalAuthGuard - [ ] Todos os endpoints com
@CurrentUsertem guard que popularequest.user - [ ] Nenhum endpoint admin acessivel sem AdminGuard
- [ ] Audit trail para todas as operacoes admin (update-user-balance, ban, etc.)
- [ ] Verificar que
@Public()esta apenas em endpoints intencionalmente publicos - [ ] Rate limiting implementado em endpoints financeiros (deposito, saque, upgrade)
8.2 Seguranca - Dados e Criptografia
- [ ] 2FA secret NAO salvo antes da confirmacao
- [ ] Webhook de pagamento valida assinatura HMAC do provedor
- [ ] Webhook de pagamento valida IP de origem (whitelist)
- [ ] Session cookies com
httpOnly,secure,sameSite=strict - [ ] Helmet configurado (X-Frame-Options, CSP, HSTS, etc.)
- [ ] Variaveis de ambiente sem segredos hardcoded
- [ ]
.envno.gitignore - [ ] Nenhum
console.logcom dados financeiros em nivel de producao - [ ] Provably fair seeds com entropia adequada (256 bits)
- [ ] Nonce incremental sem possibilidade de reutilizacao
8.3 Integridade Financeira
- [ ] Source of truth do saldo e SEMPRE TransactionRepository
- [ ] Double-entry bookkeeping em TODAS as operacoes financeiras
- [ ] Distributed locks em TODAS as operacoes de saldo
- [ ] Transacoes atomicas (Prisma $transaction) em operacoes criticas
- [ ] Bonus de deposito tem logica de negocio correta (primeiro deposito? limite?)
- [ ] Saldo cache atualizado APOS cada transacao
- [ ] BigInt em centavos para TODOS os valores monetarios
- [ ] Nenhuma operacao de ponto flutuante em calculos financeiros
8.4 Cleanup e Manutencao
- [ ] Cron job para items LOCKED >1h sem withdrawal
- [ ] Cron job para swaps expirados (>24h)
- [ ] Cron job ou mecanismo para deposits abandonados
- [ ] Dead code removido (emitWithdrawalProcessing, checkTradeStatusByProjectId)
- [ ] Dependencias mortas removidas (bullmq, kafkajs)
- [ ] Decorator @CurrentUser unificado (deletar user.decorator.ts)
- [ ] CLAUDE.md atualizado para refletir estado real
8.5 Infraestrutura
- [ ] Redis com alta disponibilidade (Sentinel ou Cluster, minimo 3 nodes)
- [ ] PostgreSQL com backups automaticos e PITR testado
- [ ] PostgreSQL com read replicas para queries pesadas
- [ ] SSL/TLS em todas as conexoes (API, WebSocket, Redis, PostgreSQL)
- [ ] Firewall restritivo (apenas portas necessarias)
- [ ] CDN para assets estaticos
- [ ] DNS com failover
- [ ] PgBouncer ou pool equivalente para PostgreSQL
8.6 Monitoramento e Alertas
- [ ] Health check endpoints ativos (/health verifica Redis, PostgreSQL, Waxpeer)
- [ ] Prometheus coletando metricas
- [ ] Grafana com dashboards de CPU, memoria, disco, rede
- [ ] Dashboard de metricas de negocio (aberturas/dia, revenue, GGR)
- [ ] Alerta: Waxpeer WebSocket desconectado >5 minutos
- [ ] Alerta: Taxa de erro HTTP >5% em janela de 5 minutos
- [ ] Alerta: Latencia P99 >2s em endpoints financeiros
- [ ] Alerta: Saldo negativo detectado em qualquer usuario
- [ ] Alerta: Lock timeout em operacao financeira
- [ ] Alerta: Redis down ou failover
8.7 Resiliencia
- [ ] Circuit breaker para API Waxpeer
- [ ] Circuit breaker para API HorsePay
- [ ] Recovery automatico para Waxpeer WebSocket
- [ ] Fallback para cache expirado quando API externa indisponivel
- [ ] BullMQ configurado com Dead Letter Queue
- [ ] Batalhas executando em worker separado (nao inline)
- [ ] Rollback procedure documentado e testado
8.8 Testes
- [ ] Testes unitarios: cobertura >80% em services e use cases
- [ ] Testes de integracao: fluxos criticos (deposito -> abertura -> saque)
- [ ] Testes de carga: resultados para 100, 500, 1000 usuarios simultaneos
- [ ] Testes de penetracao: resultado limpo (sem vulnerabilidades criticas/altas)
- [ ] Testes de chaos: Redis restart, PostgreSQL failover
- [ ] Testes de recuperacao: backup restore testado com sucesso
8.9 Operacoes
- [ ] Logs centralizados (ELK Stack ou equivalente)
- [ ] Rotacao de logs configurada
- [ ] Logs sem dados sensiveis em nivel info/warn
- [ ] Rollback procedure documentado e testado
- [ ] Runbook para incidentes comuns
- [ ] Plano de disaster recovery documentado
- [ ] Procedimento de escalonamento definido
8.10 Conformidade
- [ ] Termos de uso publicados
- [ ] Politica de privacidade publicada
- [ ] Pagina de verificacao provably fair funcional
- [ ] KYC definido para saques acima de threshold
- [ ] Anti-fraude com regras basicas (multiplos deposits, velocidade de jogo)
9. Plano de Escalabilidade
9.1 Fase 1: Ate 1.000 usuarios simultaneos
Infraestrutura recomendada:
- 1 servidor API (4+ CPU cores, 8GB+ RAM)
- 1 instancia PostgreSQL com backups automaticos
- 1 instancia Redis com persistencia RDB + AOF
- Nginx como reverse proxy com SSL termination
- PM2 com 4 workers (cluster mode)
Configuracao:
| Componente | Configuracao |
|---|---|
| Node.js | PM2 cluster mode, 4 workers |
| PostgreSQL | max_connections: 100, shared_buffers: 2GB |
| Redis | maxmemory: 2GB, maxmemory-policy: allkeys-lru |
| Nginx | worker_connections: 1024 |
| WebSocket | Socket.io com 1 instancia (sem Redis Adapter necessario) |
Estimativas de carga:
- ~50 requests/segundo
- ~500 conexoes WebSocket simultaneas
- ~10.000 transacoes/dia
- ~5GB de dados no PostgreSQL/mes
O que implementar ANTES desta fase:
- Todos os itens "Criticos" da secao 7.1
- Monitoramento basico (Prometheus + Grafana)
- Alertas via Slack para erros criticos
- Health check endpoints
- Backup automatico do PostgreSQL (diario)
Riscos aceitaveis nesta fase:
- Redis como SPOF (restart manual em ~5 minutos)
- PostgreSQL sem replicacao (restore de backup em ~30 minutos)
- Battle execution sincrona (se BullMQ nao implementado)
- Sem circuit breaker (APIs externas podem causar lentidao)
Metricas de transicao para Fase 2:
- CPU medio do servidor >60% por mais de 7 dias
- Latencia P99 >500ms em endpoints financeiros
- Mais de 800 conexoes WebSocket simultaneas
- Redis memory usage >70%
- Taxa de erro HTTP >1%
9.2 Fase 2: 1.000 a 10.000 usuarios simultaneos
Infraestrutura adicional:
- 2-3 instancias de API (load balanced)
- Redis Sentinel (1 master + 2 replicas)
- PostgreSQL replicacao (1 primary + 2 read replicas)
- PgBouncer para connection pooling
- CDN para assets estaticos (Cloudflare)
- Servidor separado para workers BullMQ
Configuracao:
| Componente | Configuracao |
|---|---|
| API | 2-3 instancias, cada uma com PM2 cluster mode |
| PostgreSQL | max_connections: 300 (via PgBouncer), particionamento mensal |
| Redis Sentinel | 3 nodes, maxmemory: 8GB por node |
| BullMQ | 4-8 workers concorrentes em servidor separado |
| WebSocket | Socket.io com Redis Adapter para multicast entre instancias |
| Load Balancer | Nginx/HAProxy com health checks, sticky sessions para WebSocket |
O que implementar ANTES desta fase:
- Redis Sentinel (elimina SPOF)
- PostgreSQL read replicas + PgBouncer
- BullMQ para batalhas (elimina bloqueio de thread)
- Socket.io Redis Adapter (multicast entre instancias)
- Circuit breaker para APIs externas (Waxpeer, HorsePay)
- Cache de live drops em Redis (TTL 10s)
- Atualizacao incremental de saldo cache
- Particionamento de Transaction e CaseOpening por mes
- Rate limiting implementado (IP, usuario, acao)
- ELK Stack para centralizacao de logs
Estimativas de carga:
- ~500 requests/segundo
- ~5.000 conexoes WebSocket simultaneas
- ~100.000 transacoes/dia
- ~50GB de dados no PostgreSQL/mes
Riscos nesta fase:
- Replication lag do PostgreSQL pode afetar leituras imediatas apos escrita
- Consistencia eventual entre instancias de API
- Failover automatico pode causar 2-5 segundos de indisponibilidade
Mitigacoes:
- Queries criticas (saldo, transacao) SEMPRE no primary
- Read replicas apenas para historico, relatorios, live drops
- Health checks com intervalo de 5 segundos
Metricas de transicao para Fase 3:
- CPU medio >70% nas instancias de API
- Latencia P99 >200ms em endpoints financeiros
- Mais de 8.000 conexoes WebSocket simultaneas
- Redis memory usage >60% em qualquer node
- Fila BullMQ com >50 jobs pendentes regularmente
9.3 Fase 3: 10.000+ usuarios simultaneos
Arquitetura de microservicos:
CDN (Cloudflare)
|
Load Balancer
(HAProxy)
|
+--------------+--------------+
| | |
API Pod 1 API Pod 2 API Pod N
(NestJS) (NestJS) (NestJS)
| | |
+--------------+--------------+
|
Redis Cluster
(6+ nodes)
|
+-----------+-----------+
| | |
PgBouncer PgBouncer PgBouncer
| | |
Primary Replica 1 Replica 2
(PostgreSQL) (PostgreSQL) (PostgreSQL)Infraestrutura:
- Kubernetes ou Docker Swarm para orquestracao
- 4-8 pods de API com auto-scaling
- Redis Cluster (6+ nodes: 3 masters + 3 replicas)
- PostgreSQL com particionamento + sharding por userId (se necessario)
- Microservicos separados para funcionalidades isolaveis
- Message queue dedicada (BullMQ com Redis Cluster)
Microservicos sugeridos:
| Servico | Responsabilidade | Pods | Comunicacao |
|---|---|---|---|
| API Gateway | Roteamento, auth, rate limiting | 4-8 | HTTP |
| Game Engine | Aberturas, upgrades | 2-4 | BullMQ |
| Battle Service | Batalhas (execucao assincrona) | 2-4 | BullMQ |
| Waxpeer Service | Integracao, tracking, swaps | 1-2 | BullMQ + WebSocket |
| Payment Service | Depositos, saques, webhooks | 1-2 | HTTP + Webhook |
| Notification Service | WebSocket, push | 2-4 | Redis Pub/Sub |
| Admin Service | Backoffice, relatorios | 1 | HTTP |
| Worker Service | BullMQ jobs genericos | 4-8 | BullMQ |
Otimizacoes necessarias nesta fase:
- CQRS (Command Query Responsibility Segregation)
- Event Sourcing para audit trail completo
- Materialized views para queries pesadas
- gRPC para comunicacao entre microservicos
- Horizontal sharding do PostgreSQL por userId
- Redis Cluster com 32GB+ de memoria total
- Auto-scaling horizontal baseado em metricas customizadas
- Connection pooling com pool_mode=transaction (PgBouncer)
- Compressao WebSocket habilitada
- Read replicas dedicadas para relatorios e backoffice
Estimativas de carga:
- ~5.000 requests/segundo
- ~50.000 conexoes WebSocket simultaneas
- ~1.000.000 transacoes/dia
- ~500GB de dados no PostgreSQL/mes
Riscos nesta fase:
- Complexidade operacional significativamente maior
- Consistencia eventual entre microservicos
- Necessidade de equipe DevOps dedicada (minimo 2 pessoas)
- Custo de infraestrutura 10-20x maior que Fase 1
- Debugging distribuido requer tooling especializado (Jaeger, Zipkin)
Decisoes arquiteturais:
- Se o sistema atingir 50.000+ usuarios simultaneos, considerar migracao para Kafka ao inves de Redis Pub/Sub para garantir ordering e durabilidade de mensagens
- Considerar separacao do banco de dados por dominio (financial DB, game DB, user DB)
- Implementar API Gateway dedicado (Kong, Envoy) ao inves de NestJS como gateway
9.4 Tabela Resumo de Transicao entre Fases
| Metrica | Fase 1 | Transicao 1->2 | Fase 2 | Transicao 2->3 | Fase 3 |
|---|---|---|---|---|---|
| Usuarios simultaneos | <1.000 | 800+ por 7 dias | 1.000-10.000 | 8.000+ por 7 dias | 10.000+ |
| Requests/segundo | <50 | >40 | 50-500 | >400 | 500+ |
| WebSocket conexoes | <500 | >400 | 500-5.000 | >4.000 | 5.000+ |
| CPU medio | <60% | >60% | <70% | >70% | -- |
| Latencia P99 (financeiro) | <500ms | >500ms | <200ms | >200ms | <100ms |
| Redis memory | <70% | >70% | <60% | >60% | -- |
| Instancias API | 1 | -- | 2-3 | -- | 4-8 |
| Redis | Single | -> Sentinel | Sentinel | -> Cluster | Cluster |
| PostgreSQL | Single | -> Replicas | 1P+2R | -> Shard | Sharded |
| Workers BullMQ | 0 (inline) | -> 2 | 2-4 | -> 8 | 8-16 |
| Downtime por falha | ~30 min | -- | ~5 min | -- | <30 seg |
| Custo mensal estimado | $50-200 | -- | $500-2.000 | -- | $2.000-10.000 |
10. Apendices
Apendice A: Mapa de Arquivos Criticos com Problemas Identificados
| Arquivo | Problema | Secao | Severidade |
|---|---|---|---|
src/presentation/controllers/raffle.controller.ts | Sem AdminGuard no draw | 2.1 | CRITICO |
src/presentation/controllers/audit.controller.ts | Sem AdminGuard em verify-chain e entity | 2.2 | CRITICO |
src/presentation/controllers/upgrade.controller.ts | Sem guard em verify | 2.3 | ALTO |
src/presentation/controllers/event.controller.ts | Sem OptionalAuthGuard em active | 2.4 | ALTO |
src/presentation/decorators/user.decorator.ts | Duplicata | 2.5 | ALTO |
src/application/services/deposit.service.ts | Bonus 10% em todos depositos | 2.9 | MEDIO |
src/application/use-cases/payment/generate-2fa-secret.use-case.ts | Secret salvo antes de confirmar | 2.10 | MEDIO |
src/application/use-cases/user/get-user-profile.use-case.ts | Usa cache ao inves de source of truth | 2.11 | MEDIO |
src/infrastructure/external-apis/waxpeer-websocket.service.ts | Max reconnect sem recovery | 2.12 | MEDIO |
src/application/services/waxpeer-withdrawal-tracker.service.ts | Batch limit 100, items LOCKED | 2.7, 2.13 | ALTO |
src/infrastructure/websocket/websocket.gateway.ts | emitWithdrawalProcessing morto | 4.1 | BAIXO |
src/infrastructure/external-apis/waxpeer.service.ts | checkTradeStatusByProjectId morto | 4.2 | BAIXO |
src/application/services/transaction.service.ts | console.log com dados financeiros | 6.4 | MEDIO |
src/application/use-cases/battle/execute-battle.use-case.ts | Execucao bloqueante inline | 6.2 | ALTO |
src/presentation/controllers/payment.controller.ts | Bracket notation para acessar propriedade privada | 2.10 | MEDIO |
Apendice B: Endpoints Sem Guard Adequado (Resumo)
| Endpoint | Guard Atual | Guard Necessario | Risco |
|---|---|---|---|
POST /raffle/:id/draw | AuthGuard | AuthGuard + AdminGuard | CRITICO |
GET /audit/verify-chain | AuthGuard | AuthGuard + AdminGuard | CRITICO |
GET /audit/entity/:type/:id | AuthGuard | AuthGuard + AdminGuard | CRITICO |
POST /upgrade/:id/verify | Nenhum explicito | @Public() ou AuthGuard | ALTO |
GET /event/active | Nenhum | OptionalAuthGuard + @Public() | ALTO |
Apendice C: Superficie de Ataque - Endpoints Publicos
Endpoints marcados com @Public() que NAO requerem autenticacao:
Autenticacao:
GET /auth/test- Endpoint de testeGET /auth/steam/login- Inicia fluxo OAuthGET /auth/steam/callback- Callback OAuth
Catalogo:
GET /cases- Lista caixasGET /cases/categories- CategoriasGET /cases/:id- Detalhes da caixaGET /cases/:id/items- Items da caixaGET /cases/:id/probabilities- Probabilidades
Live Drops:
GET /live-drops/recent- Drops recentesGET /live-drops/top- Top drops
Battles:
GET /battles/list- Lista de batalhas
Upgrade:
GET /upgrade/targets- Items alvo para upgrade
Swap:
GET /swap/targets- Items alvo para swap
Raffle:
GET /raffle/active- Sorteios ativosGET /raffle/completed- Sorteios finalizadosGET /raffle/:id- Detalhes do sorteio (OptionalAuthGuard)
Provably Fair:
POST /provably-fair/generate-seeds- Gera seedsPOST /provably-fair/verify-seed- Verifica seedPOST /provably-fair/calculate-result- Calcula resultadoPOST /provably-fair/verify-result- Verifica resultado
Items:
GET /api/items/*- Todos os endpoints de items publicos
Pagamentos:
POST /payment/deposit/webhook/:provider- Webhook de deposito
Infraestrutura:
GET /health- Health checkGET /docs- API reference (Scalar)
Aberturas:
GET /cases/openings/recent- Aberturas recentes
NOTA: Verificar se GET /auth/test deveria ser publico em producao. Endpoints de teste geralmente devem ser removidos ou desabilitados em producao.
Apendice D: Fluxo de Dados Financeiro Critico
Usuario solicita operacao financeira
|
v
[AuthGuard] - Valida sessao, IP, User-Agent
|
v
[Controller] - Recebe e valida input (DTOs com class-validator)
|
v
[Use Case] - Logica de negocio, validacoes de regra
|
v
[LockService.withUserBalanceLock(userId)] - Adquire lock Redlock (TTL 10s)
|
v
[PrismaService.$transaction()] - Inicia transacao atomica PostgreSQL
|
v
[TransactionRepository.getUserBalance()] - GROUP BY em todas as transacoes (SOURCE OF TRUTH)
|
v
[Verifica saldo >= amountCents] - Rejeita se insuficiente
|
v
[TransactionService.createDoubleEntry()] - Cria DEBIT + CREDIT atomicamente
|
v
[User.balanceCents update] - Atualiza CACHE de saldo no User
|
v
[COMMIT] - Transacao PostgreSQL commitada
|
v
[Lock RELEASE] - Redlock liberado
|
v
[WebSocketGateway.emitBalanceUpdate(userId, balanceCents: bigint)]
|
v
[Frontend recebe balanceCents (string de inteiro)] -> parseInt -> exibe com toLocaleString('pt-BR')Pontos de falha e protecoes:
- Se o lock falha: operacao nao inicia (retry ate 10x com jitter)
- Se a transacao falha: rollback automatico do PostgreSQL
- Se o WebSocket falha: saldo correto no banco, frontend atualiza no proximo GET /user/profile
- Se o Redis cai: lock nao pode ser adquirido, operacao rejeitada (seguro, nao causa inconsistencia)
Apendice E: Glossario
| Termo | Definicao |
|---|---|
| AuthGuard | Guard que valida sessao, IP e User-Agent do usuario |
| AdminGuard | Guard que verifica role ADMIN ou SUPER_ADMIN |
| OptionalAuthGuard | Guard que popula request.user se houver sessao, mas permite acesso anonimo |
| @Public() | Decorator que marca endpoint como publico (bypass do AuthGuard global) |
| @CurrentUser | Decorator que extrai usuario da request (depende de guard que popule request.user) |
| Source of Truth | Fonte unica de verdade para um dado. Para saldo: TransactionRepository |
| Cache de saldo | Campo user.balanceCents - atualizado apos cada transacao, NAO e source of truth |
| Double-entry | Toda transacao cria DEBIT + CREDIT. Soma de todas = 0 |
| SYSTEM_USER_ID | BigInt(0) - usuario virtual que representa a casa/plataforma |
| Redlock | Algoritmo de lock distribuido usando Redis |
| BullMQ | Biblioteca de filas usando Redis (instalada mas nao configurada) |
| Waxpeer | API externa de marketplace de skins CS2 |
| HorsePay | Provedor de pagamento PIX |
| Circuit Breaker | Pattern de resiliencia que para chamadas a servicos com falha (nao implementado) |
| Dead Letter Queue | Fila para jobs que falharam apos todas as tentativas (nao implementado) |
| SPOF | Single Point of Failure - componente cuja falha derruba o sistema inteiro |
| LOCKED | Status de item de inventario reservado para withdrawal em andamento |
| IN_SWAP | Status de item de inventario reservado para swap pendente |
| Provably Fair | Sistema criptografico de verificacao independente de resultados |
| FLIP | Mecanica de segunda roleta com items raros |
| Settlement | Distribuicao justa de items entre vencedores de batalha |
Documento gerado em 2026-02-11 por analise automatizada do codigo-fonte.Deve ser revisado e atualizado a cada release importante, correcao de seguranca, ou mudanca arquitetural significativa.Proxima revisao recomendada: antes do lancamento em producao.
