From aa1c528197dbf7a589d37223ffcc303cc9e8b5a6 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 13 Aug 2026 21:18:39 -0300 Subject: [PATCH] fix: botao Sair quebrado em /clientes, /log e /gerador-chave (csrf_token vazio) base.html usava {{ csrf_token }}, variavel que so algumas rotas passam explicitamente no contexto do template. Em /clientes (GET), /log e /gerador-chave a variavel nunca era passada, entao o campo oculto do formulario de logout renderizava com value="" e exigir_csrf (app/csrf.py) rejeitava o POST /logout com 403 - o usuario nao conseguia clicar em 'Sair' logo apos logar, ja que /clientes e para onde todo login cai. Corrige na raiz: usa {{ csrf_token or request.state.csrf_token }}, caindo para o valor que csrf_cookie_middleware (app/main.py) ja popula em toda requisicao quando a rota nao passar csrf_token explicitamente. Protege tambem qualquer rota futura que esqueca de passar a variavel. Testes de regressao em tests/test_main.py: confirma que GET /clientes renderiza um csrf_token nao vazio no formulario de logout, e que POST /logout funciona (303) apos visitar /clientes. Co-Authored-By: Claude Sonnet 5 --- app/templates/base.html | 2 +- tests/test_main.py | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/app/templates/base.html b/app/templates/base.html index b537fba..00ad7e1 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -35,7 +35,7 @@ {% if request.state.usuario %} Minha conta
- +
{% endif %} diff --git a/tests/test_main.py b/tests/test_main.py index 1e8f53a..3602c87 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -62,3 +62,60 @@ def test_csrf_libera_post_quando_token_bate_com_cookie(monkeypatch): "/clientes/1/excluir", cookies=cookies, data={"csrf_token": token_cookie}, follow_redirects=False ) assert resposta.status_code == 303 + + +def test_pagina_clientes_tem_csrf_token_preenchido_no_botao_sair(monkeypatch): + """ + Regressão: GET /clientes (a página onde todo login cai) não passava + csrf_token no contexto do template, então o formulário de logout em + base.html renderizava com value="" e POST /logout era rejeitado com + 403 por app/csrf.py. base.html agora usa + "{{ csrf_token or request.state.csrf_token }}" como fallback. + """ + from app.routers import clientes as router_clientes + + monkeypatch.setattr( + router_clientes.db_clientes, + "listar_clientes", + lambda busca=None, limite=100, offset=0, ordenar_por="razao", direcao="asc": [], + ) + monkeypatch.setattr(router_clientes.db_clientes, "contar_clientes", lambda busca=None, ordenar_por="razao": 0) + + client.cookies.clear() + resposta = client.get("/clientes", cookies=_cookie_sessao_admin()) + assert resposta.status_code == 200 + assert 'name="csrf_token" value=""' not in resposta.text + + import re + + match = re.search(r'name="csrf_token" value="([^"]*)"', resposta.text) + assert match is not None + assert len(match.group(1)) > 0 + + +def test_logout_funciona_depois_de_visitar_clientes(monkeypatch): + """ + Regressão complementar: simula o fluxo real — visitar /clientes (onde + o cookie csrf_token é definido pelo middleware) e então enviar + POST /logout usando o csrf_token real do cookie do navegador, sem + passar csrf_token manualmente no contexto do template. + """ + from app.routers import clientes as router_clientes + + monkeypatch.setattr( + router_clientes.db_clientes, + "listar_clientes", + lambda busca=None, limite=100, offset=0, ordenar_por="razao", direcao="asc": [], + ) + monkeypatch.setattr(router_clientes.db_clientes, "contar_clientes", lambda busca=None, ordenar_por="razao": 0) + + client.cookies.clear() + client.cookies.update(_cookie_sessao_admin()) + resposta_clientes = client.get("/clientes") + assert resposta_clientes.status_code == 200 + + token_cookie = client.cookies.get("csrf_token") + assert token_cookie + + resposta_logout = client.post("/logout", data={"csrf_token": token_cookie}, follow_redirects=False) + assert resposta_logout.status_code == 303