Skip to content

Segurança

O CSGOFlip implementa múltiplas camadas de segurança para proteger usuários, transações financeiras e a integridade do sistema.

Visão Geral das Camadas

┌─────────────────────────────────────────────────────────────────┐
│                      CAMADA 1: REDE                              │
│  • Cloudflare WAF    • DDoS Protection    • SSL/TLS             │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                    CAMADA 2: APLICAÇÃO                           │
│  • Rate Limiting    • CORS    • Helmet    • Input Validation    │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                   CAMADA 3: AUTENTICAÇÃO                         │
│  • Steam OAuth    • Session Management    • 2FA (TOTP)          │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                   CAMADA 4: AUTORIZAÇÃO                          │
│  • Guards    • Role-Based Access    • Resource Ownership        │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                    CAMADA 5: DADOS                               │
│  • Distributed Locks    • Double-Entry    • Audit Trail         │
└─────────────────────────────────────────────────────────────────┘

Autenticação

Steam OAuth 2.0

Não armazenamos senhas. A autenticação é delegada ao Steam:

Gestão de Sessões

Sessões são armazenadas no Redis com TTL de 7 dias:

typescript
interface Session {
  userId: bigint;
  steamId: string;
  ip: string;
  userAgent: string;
  deviceId?: string;
  createdAt: Date;
  lastActivity: Date;
}

// Estrutura no Redis
// Key: session:{sessionId}
// Value: JSON da sessão
// TTL: 604800 segundos (7 dias)

Benefícios:

  • ✅ Logout instantâneo (deleta a sessão)
  • ✅ Ver todos os dispositivos conectados
  • ✅ Logout de todos os dispositivos
  • ✅ Detectar sessões suspeitas (IP diferente)

2FA (Two-Factor Authentication)

Para operações sensíveis (saques), exigimos 2FA via TOTP:

typescript
// 1. Usuário ativa 2FA
const secret = speakeasy.generateSecret({ length: 20 });
// Retorna QR code para Google Authenticator

// 2. Em cada saque, valida o código
const isValid = speakeasy.totp.verify({
  secret: user.twoFactorSecret,
  encoding: 'base32',
  token: userCode,
  window: 1, // Permite 1 código anterior/posterior
});

Proteções de Rede

Rate Limiting

Limitamos requisições por IP, usuário e ação:

typescript
@Module({
  imports: [
    ThrottlerModule.forRoot([
      {
        name: 'short',
        ttl: 1000,   // 1 segundo
        limit: 10,   // 10 requisições
      },
      {
        name: 'medium',
        ttl: 10000,  // 10 segundos
        limit: 50,   // 50 requisições
      },
      {
        name: 'long',
        ttl: 60000,  // 1 minuto
        limit: 200,  // 200 requisições
      },
    ]),
  ],
})

Limites específicos:

AçãoLimiteJanela
Login5 tentativas15 minutos
Abertura de caixa10/minutoPor usuário
Criar batalha5/minutoPor usuário
Depósito3/horaPor usuário
Saque2/horaPor usuário

CORS (Cross-Origin Resource Sharing)

Apenas origens autorizadas podem fazer requisições:

typescript
app.enableCors({
  origin: [
    'https://csgoflip.com',
    'https://admin.csgoflip.com',
    process.env.NODE_ENV === 'development' && 'http://localhost:3000',
  ].filter(Boolean),
  credentials: true, // Permite cookies
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
});

Headers de Segurança

Usando Helmet (adaptado para Fastify):

typescript
app.register(helmet, {
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"], // Para Next.js
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
  },
});

Validação de Entrada

ValidationPipe Global

Todas as requisições passam por validação automática:

typescript
app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,        // Remove campos não declarados
    forbidNonWhitelisted: true, // Erro se campo extra
    transform: true,        // Transforma tipos
    transformOptions: {
      enableImplicitConversion: true,
    },
  }),
);

DTOs com class-validator

typescript
export class OpenCaseDto {
  @IsNotEmpty()
  @IsString()
  @Length(1, 32)
  caseId: string;

  @IsOptional()
  @IsInt()
  @Min(1)
  @Max(10)
  quantity?: number = 1;

  @IsOptional()
  @IsString()
  @Length(32, 64)
  clientSeed?: string;
}

Sanitização

Prevenção contra XSS e injection:

typescript
@Injectable()
export class SanitizePipe implements PipeTransform {
  transform(value: any) {
    if (typeof value === 'string') {
      // Remove tags HTML
      return value.replace(/<[^>]*>/g, '');
    }
    return value;
  }
}

Proteção de Dados Financeiros

Distributed Locks (Redlock)

Previne race conditions em operações de saldo:

typescript
async debitBalance(userId: bigint, amount: bigint) {
  // Adquire lock exclusivo para este usuário
  const lock = await this.redlock.acquire(
    [`lock:user:balance:${userId}`],
    5000, // 5 segundos de timeout
  );

  try {
    // Operação atômica dentro do lock
    const balance = await this.getBalance(userId);
    
    if (balance < amount) {
      throw new InsufficientBalanceError();
    }
    
    await this.createDebitTransaction(userId, amount);
  } finally {
    // SEMPRE libera o lock
    await lock.release();
  }
}

Race Condition Prevenida

Sem o lock, duas requisições simultâneas poderiam:

  1. Ler saldo: R$ 100
  2. Ambas verificam: R$ 100 >= R$ 80 ✓
  3. Ambas debitam: R$ 80
  4. Saldo final: -R$ 60 (ERRO!)

Com lock, a segunda requisição espera a primeira terminar.

Double-Entry Bookkeeping

Toda transação tem débito E crédito:

typescript
async transfer(fromUserId: bigint, toUserId: bigint, amount: bigint) {
  await this.prisma.$transaction(async (tx) => {
    // Cria DEBIT para quem paga
    const debit = await tx.transaction.create({
      data: {
        userId: fromUserId,
        type: 'DEBIT',
        amountCents: -amount,
        reason: 'TRANSFER_OUT',
      },
    });

    // Cria CREDIT para quem recebe
    const credit = await tx.transaction.create({
      data: {
        userId: toUserId,
        type: 'CREDIT',
        amountCents: amount,
        reason: 'TRANSFER_IN',
        relatedTransactionId: debit.id, // Liga as transações
      },
    });

    // Atualiza o debit com referência ao credit
    await tx.transaction.update({
      where: { id: debit.id },
      data: { relatedTransactionId: credit.id },
    });
  });
}

Audit Trail Imutável

Todas as ações são logadas com hash encadeado (blockchain-like):

typescript
interface AuditLog {
  id: bigint;
  userId: bigint;
  action: string;
  entityType: string;
  entityId: bigint;
  oldData: JsonValue;
  newData: JsonValue;
  metadata: JsonValue;
  previousHash: string; // Hash do registro anterior
  currentHash: string;  // Hash deste registro
  createdAt: Date;
}

// Cálculo do hash
function calculateHash(log: AuditLog, previousHash: string): string {
  const data = JSON.stringify({
    id: log.id,
    userId: log.userId,
    action: log.action,
    entityType: log.entityType,
    entityId: log.entityId,
    oldData: log.oldData,
    newData: log.newData,
    previousHash,
  });
  
  return crypto.createHash('sha256').update(data).digest('hex');
}

Benefícios:

  • ✅ Impossível alterar registros antigos (hash quebraria)
  • ✅ Detecta adulteração verificando a cadeia
  • ✅ Auditoria completa de todas as ações

Autorização

Guards

typescript
// Auth Guard - Verifica sessão válida
@Injectable()
export class AuthGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const sessionId = request.cookies['sessionId'];
    
    if (!sessionId) {
      throw new UnauthorizedException('Session required');
    }
    
    const session = await this.sessionService.validate(sessionId);
    if (!session) {
      throw new UnauthorizedException('Invalid session');
    }
    
    request.user = session.user;
    return true;
  }
}

// Admin Guard - Verifica role de admin
@Injectable()
export class AdminGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    
    if (!['ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
      throw new ForbiddenException('Admin access required');
    }
    
    return true;
  }
}

Resource Ownership

Usuários só podem acessar seus próprios recursos:

typescript
@Get('inventory')
async getInventory(@CurrentUser() user: User) {
  // Usuário só vê SEU inventário
  return this.inventoryService.getUserInventory(user.id);
}

@Post('battles/:id/join')
async joinBattle(
  @Param('id') battleId: string,
  @CurrentUser() user: User,
) {
  // Verifica se batalha existe e está aberta
  const battle = await this.battleService.findById(battleId);
  
  if (battle.creatorId === user.id) {
    throw new BadRequestException('Cannot join your own battle');
  }
  
  // Continua...
}

Proteções Específicas de Gambling

Provably Fair

Garante que resultados não são manipulados:

typescript
// ANTES do jogo
const serverSeed = crypto.randomBytes(32).toString('hex');
const serverSeedHash = crypto.createHash('sha256')
  .update(serverSeed)
  .digest('hex');

// Envia APENAS o hash para o usuário
// O serverSeed fica secreto

// APÓS o jogo
// Revela o serverSeed para verificação
// Usuário pode calcular: SHA256(serverSeed) === serverSeedHash?

Anti-Sniping (Batalhas)

Previne que usuários vejam resultados antes de entrar:

typescript
async joinBattle(battleId: bigint, userId: bigint) {
  // 1. Lock da batalha
  const lock = await this.redlock.acquire([`lock:battle:${battleId}`], 5000);
  
  try {
    const battle = await this.battleRepo.findById(battleId);
    
    // 2. Verifica que batalha não começou
    if (battle.status !== 'WAITING') {
      throw new BadRequestException('Battle already started');
    }
    
    // 3. Adiciona jogador ANTES de gerar seeds
    await this.battleRepo.addPlayer(battleId, userId);
    
    // 4. Se ficou cheio, gera seeds e inicia
    if (battle.players.length === battle.maxPlayers) {
      const serverSeed = this.generateServerSeed();
      await this.battleRepo.start(battleId, serverSeed);
    }
  } finally {
    await lock.release();
  }
}

Cooldown de Saque

Previne saques imediatos após depósito (anti-lavagem):

typescript
async requestWithdrawal(userId: bigint, amount: bigint) {
  const lastDeposit = await this.depositRepo.getLastConfirmed(userId);
  
  if (lastDeposit) {
    const hoursSinceDeposit = 
      (Date.now() - lastDeposit.confirmedAt.getTime()) / (1000 * 60 * 60);
    
    if (hoursSinceDeposit < 24) {
      throw new BadRequestException(
        'Withdrawals available 24 hours after deposit'
      );
    }
  }
  
  // Continua com saque...
}

Monitoramento de Segurança

Alertas Automáticos

typescript
// Alerta: Muitas tentativas de login falhadas
if (failedLogins > 10) {
  await this.alertService.send({
    severity: 'HIGH',
    type: 'BRUTE_FORCE_ATTEMPT',
    ip: request.ip,
    userId: attemptedUserId,
  });
}

// Alerta: Saque acima do normal
if (withdrawal.amount > user.avgWithdrawal * 5) {
  await this.alertService.send({
    severity: 'MEDIUM',
    type: 'UNUSUAL_WITHDRAWAL',
    userId: user.id,
    amount: withdrawal.amount,
  });
}

// Alerta: Login de novo IP
if (!knownIps.includes(request.ip)) {
  await this.alertService.send({
    severity: 'LOW',
    type: 'NEW_IP_LOGIN',
    userId: user.id,
    ip: request.ip,
  });
}

Logs Estruturados

Todos os logs seguem formato estruturado para análise:

typescript
this.logger.log({
  event: 'WITHDRAWAL_REQUESTED',
  userId: user.id,
  amount: amount,
  method: 'PIX',
  ip: request.ip,
  userAgent: request.headers['user-agent'],
  timestamp: new Date().toISOString(),
});

Checklist de Segurança

Implementado ✅

  • [x] Steam OAuth (sem senhas)
  • [x] Sessions Redis (revogáveis)
  • [x] 2FA para saques
  • [x] Rate limiting multi-camada
  • [x] CORS whitelist
  • [x] Validação de entrada (ValidationPipe)
  • [x] Distributed locks (race conditions)
  • [x] Double-entry bookkeeping
  • [x] Audit trail imutável
  • [x] Provably Fair
  • [x] Guards de autenticação/autorização
  • [x] Headers de segurança (Helmet)

Recomendado para Produção

  • [ ] Cloudflare WAF
  • [ ] DDoS protection
  • [ ] IP reputation checking
  • [ ] Device fingerprinting
  • [ ] Fraud detection ML
  • [ ] PCI DSS compliance (se cartão)
  • [ ] Penetration testing regular

Arquivos Fonte Relacionados

Principais Arquivos de Segurança

  • src/presentation/guards/auth.guard.ts - Guard de autenticação
  • src/presentation/guards/admin.guard.ts - Guard de admin
  • src/infrastructure/auth/session.service.ts - Gestão de sessões
  • src/infrastructure/auth/two-factor.service.ts - 2FA
  • src/infrastructure/locks/lock.service.ts - Distributed locks
  • src/infrastructure/audit/audit.service.ts - Audit trail
  • src/application/services/provably-fair.service.ts - Provably Fair

Documentação Técnica CSGOFlip