63 lines
2.3 KiB
Python
63 lines
2.3 KiB
Python
"""
|
|
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
|