Pular para o conteúdo

Autenticação

Como alguém entra — e como alguém deixa de entrar: convite, registro aberto com aprovação, login social nos quatro provedores, a página única de login opcional (/login), proteção anti-robô, os três estados da conta e o que a rota pública / mostra (e o que ela deliberadamente não mostra).

A ordem exata das checagens de acesso a um painel — indisponibilidade, aprovação pendente, master_global, papel — está no diagrama Regra de acesso ao painel (DG-03), na página de arquitetura em diagramas.

Sequência: login por senha e 2FA

sequenceDiagram
%% DG-04
accTitle: Login por senha e segundo fator
accDescr: A checagem canAccessPanel() vem antes de qualquer desafio de 2FA, que só aparece dentro de um bloco condicional no próximo request ao painel.
  participant visitante as Visitante
  participant tela_login as Tela de login
  participant vendor_login as Login (Filament)
  participant authenticate as Authenticate
  participant authenticate_session as AuthenticateSession
  participant must_two_factor as MustTwoFactor
  participant locker as Locker
  participant exigir_email as ExigirEmailVerificado (/app, KIT_REGISTRO_VERIFICAR_EMAIL)
  participant resposta_login as RespostaDeLogin
  visitante->>tela_login: credenciais
  tela_login->>vendor_login: authenticate()
  note over vendor_login: rate limit 5 tentativas
  alt conta indisponível (senha certa)
    vendor_login-->>visitante: recusa (403) via ContaIndisponivelController
  else canAccessPanel() permite
    vendor_login->>resposta_login: attemptWhen, sessão regenerada
    resposta_login-->>visitante: decide o destino
    visitante->>authenticate: request seguinte ao painel
    authenticate->>authenticate_session: sessão validada
    authenticate_session->>must_two_factor: checagem de 2FA
    alt 2FA ligado na conta
      must_two_factor-->>visitante: desafio 2FA
    end
    must_two_factor->>locker: checa bloqueio (ociosidade/manual)
    locker->>exigir_email: e-mail verificado?
  end

A tela de login do kit intercepta a falha do Filament só para explicar a conta indisponível (app/Filament/Pages/Auth/TelaLogin.php:authenticate:196); o rate limit de 5 tentativas é do próprio Filament (vendor/filament/filament/src/Auth/Pages/Login.php:rateLimit(5):70), que também decide com canAccessPanel() (vendor/filament/filament/src/Auth/Pages/Login.php:canAccessPanel:188). O destino do login é sempre app(LoginResponse::class), vinculado a RespostaDeLogin (app/Providers/KitServiceProvider.php:LoginResponse:847). A pilha do próximo request (ordem real, conferida com php artisan route:list --json) é Authenticate → AuthenticateSession → MustTwoFactor → Locker → ExigirEmailVerificado (só obrigatório no /app, com a chave ligada).

Sessão autenticada

stateDiagram-v2
%% DG-10
accTitle: Sessão autenticada
accDescr: Depois do login, a sessão alterna entre autenticada, aguardando 2FA, bloqueada e encerrada, conforme a ociosidade, o bloqueio manual e as tentativas de senha.
  state "Autenticada" as autenticada
  state "Aguardando 2FA" as aguardando_2fa
  state "Bloqueada" as bloqueada
  state "Encerrada" as encerrada
  [*] --> autenticada
  autenticada --> aguardando_2fa : ainda não confirmou o 2FA
  aguardando_2fa --> autenticada : confirma o 2FA
  autenticada --> bloqueada : ociosidade 1800s ou bloqueio manual
  bloqueada --> autenticada : senha correta
  bloqueada --> encerrada : 5 tentativas erradas (force logout)
  autenticada --> encerrada : logout manual

O desafio de 2FA só existe para quem o confirmou antes — é o MustTwoFactor do próprio diagrama de cima. O bloqueio por ociosidade usa config('lockscreen.idle_timeout') = 1800 (config/lockscreen.php:idle_timeout:16); os três painéis compartilham o mesmo limite de tentativas com força de logout (app/Providers/Filament/AdminPanelProvider.php:enableRateLimit:268, app/Providers/Filament/AppPanelProvider.php:enableRateLimit:375, app/Providers/Filament/InfraPanelProvider.php:enableRateLimit:291).

Desbloquear leva de volta à página que a trava interceptou, como o intended do login do Laravel: a URL pedida (com a query) quando a ociosidade travou a sessão, ou a última que a pessoa tentou abrir com ela já travada — pela senha e pela volta do provedor social da mesma conta. Sem página guardada (a tela aberta por favorito), vai à raiz do painel; no /app, à organização em que a pessoa estava, se ela ainda a acessa. Pedido que espera JSON e chamada Livewire não viram destino, e outra conta que entra pela tela não herda a página. Quem guarda a página é App\Http\Middleware\BloquearSessaoComRetorno, que substitui o middleware do pacote por bind no AppServiceProvider (o pacote só guardava a página no bloqueio manual).