Skip to content

Autenticação

O ProCases utiliza Steam OpenID 2.0 para autenticação, com sessões opacas no Redis (não JWT) para gerenciamento de estado e 2FA opcional (TOTP) para saques em dinheiro acima do threshold.

Visão Geral

┌─────────────────────────────────────────────────────────────────┐
│                     FLUXO DE AUTENTICAÇÃO                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌──────────┐      ┌──────────┐      ┌──────────┐              │
│   │  Steam   │      │  Backend │      │  Redis   │              │
│   │ OpenID   │◄────►│   API    │◄────►│ Sessions │              │
│   └──────────┘      └──────────┘      └──────────┘              │
│        │                  │                 │                    │
│        │                  │                 │                    │
│        ▼                  ▼                 ▼                    │
│   ┌──────────┐      ┌──────────┐      ┌──────────┐              │
│   │  Dados   │      │  Sessão  │      │  TTL     │              │
│   │ do Steam │      │  Criada  │      │  7 dias  │              │
│   └──────────┘      └──────────┘      └──────────┘              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Componentes do Sistema

1. Steam OpenID

  • Login via conta Steam (OpenID 2.0, biblioteca openidRelyingParty)
  • Não armazenamos senhas
  • Dados obtidos: steamId, username, avatarUrl, profileUrl
  • Steam ID validado com regex de 17 dígitos (^\d{17}$)

2. Sessions (Redis)

  • Session ID de 32 bytes aleatórios (randomBytes(32).toString('hex') → 64 caracteres hex)
  • Armazenamento no Redis via ioredis
  • TTL configurável por SESSION_MAX_AGE (default 604800 = 7 dias)
  • Suporte a múltiplos dispositivos (Set user:sessions:{userId})
  • Revogação instantânea (session-based, não JWT)

3. 2FA (Two-Factor Authentication)

  • TOTP (Time-based One-Time Password) via speakeasy (secret base32, window: 1)
  • Google Authenticator / Authy compatível
  • Obrigatório para saque em dinheiro acima do threshold (WITHDRAWAL_2FA_THRESHOLD_CENTS, default R$ 100)

Fluxo Simplificado

1. Usuário clica "Login with Steam"
2. GET /api/auth/steam/login → redirect 302 para Steam
3. Steam autentica e retorna para STEAM_RETURN_URL
4. Backend valida a asserção OpenID (verifyAssertion)
5. SteamCallbackUseCase cria/atualiza usuário
6. Sessão criada no Redis + cookie sessionId
7. Redirect para {FRONTEND_URL}/auth/callback?success=true&sessionId=...
8. Usuário está logado

Endpoints

Prefixo real

As rotas de autenticação vivem sob @Controller('api/auth'). As rotas de 2FA não ficam em /api/auth — pertencem ao PaymentController (/payment/2fa/*), pois 2FA é usado no fluxo de saque.

MétodoEndpointDescrição
GET/api/auth/steam/loginInicia login Steam (redirect 302)
GET/api/auth/steam/callbackCallback do Steam (valida asserção, cria sessão)
GET/api/auth/meDados do usuário logado
POST/api/auth/logoutLogout da sessão atual
POST/api/auth/logout-allLogout de todos os dispositivos
GET/api/auth/sessionsLista sessões ativas do usuário
DELETE/api/auth/sessions/:sessionIdRevoga uma sessão específica (só do próprio usuário)
POST/payment/2fa/setupGera secret + QR Code de 2FA
POST/payment/2fa/enableAtiva 2FA com verificação de token
POST/payment/2fa/disableDesativa 2FA (exige token)
POST/payment/2fa/verifyVerifica um token 2FA

Proteções de Segurança

ProteçãoDescrição
Session-basedRevogação instantânea (delete no Redis)
Session bindingRejeita se IP ou User-Agent divergem da criação (anti-hijack)
Cookie secureApenas HTTPS em produção
SameSitenone em produção, lax em dev
Rate limiting10 req/min em login e callback (@Throttle)
Redirect allowlistisAllowedRedirect só aceita host do frontend/subdomínios
Username não sobrescritoRe-login não altera o username (defesa contra impersonation via Steam API)
2FATOTP obrigatório em saques de dinheiro acima do threshold

Cookie sessionId não é HttpOnly

No callback, o cookie sessionId é setado com httpOnly: false — o frontend precisa ler o sessionId (também devolvido na query string do redirect) para reenviar via Authorization: Bearer em ambientes cross-site. A proteção contra hijack vem do session binding (IP + User-Agent), não do flag HttpOnly.

Diagrama de Estados

┌─────────────┐
│  Visitante  │
│ (não logado)│
└──────┬──────┘
       │ Login Steam

┌─────────────┐
│   Logado    │
│  (sessão    │
│   ativa)    │
└──────┬──────┘

       ├──────────────────┬──────────────────┐
       │                  │                  │
       ▼                  ▼                  ▼
┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Logout    │    │  Suspenso/  │    │  Binding    │
│  (sessão    │    │   Banido    │    │  mismatch   │
│  revogada)  │    │ (status ≠   │    │  (401 no    │
│             │    │   ACTIVE)   │    │  AuthGuard) │
└─────────────┘    └─────────────┘    └─────────────┘

Guard global

O AuthGuard é registrado como APP_GUARD (global). Toda request autenticada faz userRepository.findById(session.userId) e bloqueia se user.status !== 'ACTIVE'. Rotas públicas usam o decorator @Public().

Páginas desta Seção

Documentação Técnica - ProCases