Pular para o conteúdo principal

Fluxo de Autenticação

Visão geral

O auth usa ApiClient no server e no client, com sessão baseada em cookie jwt_token HttpOnly.

┌──────────────────────────────────────────────┐
│ Server-side (loader) │
│ api.server.ts → ApiClient com/sem token │
│ Usado para: brand loading, auth profile │
├──────────────────────────────────────────────┤
│ Client-side (browser) │
│ auth.client.ts → ApiClient sem ler cookie │
│ Usado para: ações de UI pós-login │
└──────────────────────────────────────────────┘

Fluxo de inicialização

DefaultLayout monta
├── <AuthInitializer />
│ ├── useAuthInit():
│ │ └── Recebe auth do loader SSR → sincroniza user/userInfo no Zustand + marca hydrated
│ │ (NÃO chama initAuthService — apenas sync de dados)
│ ├── useAuthProfileSync():
│ │ └── Mantém profile atualizado quando necessário
│ └── useValidationRuntimeSync():
│ └── Computa allValidations e sincroniza com store

├── <WalletInitializer />
│ └── Roda em paralelo com AuthInitializer
│ └── Inicializa wallet state (saldo, transações)

O AuthInitializer chama 3 hooks: useAuthInit, useAuthProfileSync e useValidationRuntimeSync. O WalletInitializer roda ao lado do AuthInitializer, não dentro dele.

Login

Login e registro passam por API routes no server, não por chamadas diretas ao authService:

LoginModal
├── Usuário preenche email/CPF + senha
├── POST /api/auth/login (API route no server)
│ └── Server: authService.login() + Set-Cookie HttpOnly
├── API retorna: { user, userInfo, type? }
│ ├── type === 'two_factor_code' → modal 2FA (TODO)
│ ├── type === 'cancelled_account' → aviso conta cancelada
│ └── sem type → sucesso
├── setUser(user, userInfo) no Zustand
└── Fecha modal

Registro dinâmico (single-step)

RegisterModal
├── Usuário preenche formulário dinâmico (email, senha, telefone, documento, etc. conforme authConfig)
├── POST /api/auth/register (API route no server)
│ └── Server: valida payload + resolve endpoint em runtime
│ ├── /bff/register-simplified
│ ├── /auth/register
│ ├── /auth/register/simplified
│ └── /bff/social/{provider}/registerSimplified
│ + Set-Cookie HttpOnly
├── API retorna: { user, userInfo } (mesmo formato do login)
├── setUser(user, userInfo) no Zustand
└── Fecha modal

O fluxo social inicia em /api/auth/social/:provider (route interna do template), que redireciona para o provider BFF e retorna ao modal com os parâmetros do callback.

Nota: tanto login quanto registro passam pelas API routes (/api/auth/login, /api/auth/register) para que o cookie HttpOnly seja setado no server-side. O client nunca manipula o token diretamente.

Token storage

  • Cookie: jwt_token, 30 dias, path=/, SameSite=Lax, HttpOnly, Secure
  • Leitura do token: somente no server (getTokenFromRequest)
  • Zustand: mantém user, userInfo, isAuthenticated, hydrated, authModal

Logout

Header → botão "Sair"
├── POST /api/auth/logout (API route no server)
│ └── Server: authService.logout() + Set-Cookie de remoção (HttpOnly)
├── clearAuth() no Zustand
│ └── Também chama useWalletStore.getState().clearWallet() (side effect cross-store)

clearAuth — Side effect cross-store

O clearAuth() da auth store também limpa a wallet store:

// auth.ts
clearAuth: () => {
set({ user: null, userInfo: null, isAuthenticated: false });
useWalletStore.getState().clearWallet();
}

Isso garante que dados financeiros do usuário anterior não permaneçam no estado após logout ou expiração de sessão.

Tratamento de 401

Qualquer request protegido no server que falhar em autenticação resulta em estado não autenticado no loader/action. No client, a UI reflete esse estado via clearAuth() e novo ciclo de loader.

Validações pós-login

Após o login (ou page refresh com cookie), o sistema de validações avalia automaticamente se o usuário tem pendências obrigatórias:

setUser(user, userInfo)


useValidationRuntimeSync
├── buildValidationSnapshot(user, userInfo)
└── fetchAllValidations(config, snapshot)


allValidations.hasPending?
├── force/regulatory/global → ValidationBlockerOverlay (overlay bloqueante)
└── false → navegação normal

Três paths de bloqueio (em ordem de prioridade):

  1. forceforceRequestKyc = true no perfil (backend forçou KYC)
  2. regulatoryshowPendingDataFlow = true no perfil (dados regulatórios pendentes, ex: endereço)
  3. globalconfig.global.active = true e módulos do global não satisfeitos

Quando há pendência, o DefaultLayout renderiza o ValidationBlockerOverlay sobre o site (com blur e anti-tamper). O usuário precisa completar as validações para continuar.

Além do bloqueio global, cada contexto (casino, deposit, withdraw, etc.) é avaliado separadamente via ValidationStepsModal quando o usuário tenta executar a ação correspondente.

Ver Validações para detalhes completos.

Arquivos relevantes

ArquivoPapel
app/store/auth.tsZustand store: user, userInfo, isAuthenticated, hydrated, authModal + clearAuth (limpa wallet)
app/store/wallet.tsZustand store: wallet state + clearWallet (chamado pelo clearAuth)
app/services/auth.client.tsSingleton AuthService + ApiClient client-side
app/utils/cookie.server.tsgetTokenFromRequest, makeSetTokenHeader, makeDeleteTokenHeader
app/hooks/useAuthInit.tsSync do auth do loader SSR para store (NÃO chama initAuthService)
app/hooks/useAuthProfileSync.tsSync de profile atualizado
app/hooks/useValidationRuntimeSync.tsComputa allValidations e sincroniza com store
app/components/auth/AuthInitializer.tsxComponente invisível que chama useAuthInit + useAuthProfileSync + useValidationRuntimeSync
app/components/wallet/WalletInitializer.tsxComponente invisível que inicializa wallet (roda ao lado do AuthInitializer)
app/components/auth/LoginModal.tsxModal de login (POST /api/auth/login)
app/components/auth/RegisterModal.tsxModal de registro (POST /api/auth/register)
app/components/layout/Header.tsxAuth-aware: mostra user info ou botões
app/store/validationRuntime.tsArmazena resultado das validações
app/components/validation/ValidationBlockerOverlay.tsxOverlay bloqueante