Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Versioning follows [SemVer](https://semver.org/): **MAJOR.MINOR.PATCH**

---

## [1.32.4] — 2026-06-09

### Fixed
- **Login não acontecia: o botão "recarregava" a tela de login (regressão da v1.31.0).** Após o logout, ao logar com credenciais corretas e sem `?next=`, a sessão era criada mas o usuário voltava para `/login` (ou caía numa página "Redirecting… target URL:" em branco) — sem mensagem de erro, dando a impressão de senha errada; remover o `/login` da URL revelava que já estava logado. Causa: o fix de open redirect da 1.31.0 passou a validar o destino só por `urlparse().netloc`/`.scheme`, e a string vazia (login sem `next`) passava nesse teste e virava `redirect("")` — que o navegador resolve recarregando a própria `/login`. Agora o redirect pós-login só segue o `next` quando é um caminho relativo de verdade (começa com `/` e não `//`); sem `next`, vai para o dashboard. A barreira anti-open-redirect do CodeQL é preservada. Teste de regressão cobrindo o login sem `next`.

## [1.32.3] — 2026-06-09

### Changed
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.32.3
1.32.4
6 changes: 5 additions & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,12 @@ def _promote_session(user, remember, ip, next_url=""):
# EXATAMENTE o snippet recomendado pelo CodeQL para py/url-redirection:
# navegadores tratam "\" como "/", então remove as barras invertidas e checa
# `urlparse(target).netloc`/`.scheme` inline — caminho relativo é seguro.
# Exige um caminho relativo de verdade (começa com "/" mas não "//"): sem
# `next`, target="" passaria no teste de netloc/scheme e cairia num
# `redirect("")`, que recarrega a própria /login (regressão da v1.31.0).
target = (next_url or "").replace("\\", "")
if not urlparse(target).netloc and not urlparse(target).scheme:
if target.startswith("/") and not target.startswith("//") \
and not urlparse(target).netloc and not urlparse(target).scheme:
return redirect(target)
return redirect(url_for("dashboard"))

Expand Down
11 changes: 11 additions & 0 deletions tests/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ def test_login_honors_local_next(client):
assert resp.headers["Location"].endswith("/spools")


def test_login_without_next_goes_to_dashboard(client):
"""Login normal (sem ?next=) NÃO pode cair num redirect("") — que recarrega
a própria /login (regressão da v1.31.0). Tem que ir pro dashboard."""
resp = client.post("/login",
data={"username": ADMIN_USER, "password": ADMIN_PASS})
assert resp.status_code == 302
loc = resp.headers["Location"]
assert loc not in ("", "/login")
assert not loc.endswith("/login")


# ── Troca de senha forçada (rec 5) ───────────────────────────────────────────

def test_admin_bootstrap_requires_password_change(db):
Expand Down