""" Rotas de login/logoff. O painel não usa mais HTTP Basic Auth (ver app/auth.py) — quem não está logado é redirecionado para GET /login pelo exception handler de NaoAutenticado (registrado em app/main.py). """ from typing import Optional from urllib.parse import urlencode from fastapi import APIRouter, Depends, Form, Request from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from .. import session from ..auth import UsuarioLogado, autenticar_credenciais, exigir_login from ..csrf import exigir_csrf, token_do_contexto router = APIRouter() templates = Jinja2Templates(directory="app/templates") def _proximo_seguro(next_: Optional[str]) -> str: """Só aceita um path interno (começa com "/", sem "//") — nunca uma URL externa, para não virar um open redirect via POST /login?next=...""" if next_ and next_.startswith("/") and not next_.startswith(("//", "/\\")): return next_ return "/clientes" @router.get("/login", response_class=HTMLResponse) def form_login(request: Request, next: Optional[str] = None): return templates.TemplateResponse( request, "login.html", {"csrf_token": token_do_contexto(request), "next": next or ""} ) @router.post("/login", dependencies=[Depends(exigir_csrf)]) def login( request: Request, usuario: str = Form(...), senha: str = Form(...), next: Optional[str] = Form(None), ): usuario_logado = autenticar_credenciais(usuario, senha) if usuario_logado is None: return templates.TemplateResponse( request, "login.html", {"csrf_token": token_do_contexto(request), "next": next or "", "erro": "Usuário ou senha inválidos."}, ) destino = _proximo_seguro(next) resposta = RedirectResponse(url=destino, status_code=303) token = session.criar_token(usuario_logado.login, usuario_logado.nome, usuario_logado.perfil) resposta.set_cookie(session.SESSION_COOKIE_NAME, token, httponly=True, samesite="lax") return resposta @router.post("/logout", dependencies=[Depends(exigir_csrf)]) def logout(usuario: UsuarioLogado = Depends(exigir_login)): resposta = RedirectResponse(url="/login", status_code=303) resposta.delete_cookie(session.SESSION_COOKIE_NAME) return resposta