git-subtree-dir: gerenciador git-subtree-mainline:f4f546c928git-subtree-split:34cc9c75a5
45 lines
2.0 KiB
Python
45 lines
2.0 KiB
Python
"""
|
|
Proteção CSRF via "double-submit cookie": o middleware registrado em
|
|
app/main.py garante que todo navegador que acessa o painel tenha um cookie
|
|
"csrf_token" (gerado uma vez, aleatório, HttpOnly). Todo formulário que
|
|
muda estado (POST) embute esse mesmo valor como campo oculto — os
|
|
templates leem de request.state.csrf_token, que o middleware preenche a
|
|
cada requisição. Toda rota POST que muda estado usa Depends(exigir_csrf),
|
|
que confere se o valor enviado no formulário bate com o valor do cookie.
|
|
|
|
Por quê isso barra CSRF: um site hostil consegue forçar o navegador da
|
|
vítima a mandar um POST para o painel (o cookie vai junto automaticamente),
|
|
mas não consegue LER o valor do cookie para replicá-lo como campo oculto do
|
|
formulário forjado — só JavaScript rodando na própria origem do painel
|
|
consegue (e não roda em outra origem, por causa do same-origin policy).
|
|
"""
|
|
import secrets
|
|
|
|
from fastapi import HTTPException, Request, status
|
|
|
|
CSRF_COOKIE_NAME = "csrf_token"
|
|
|
|
|
|
def gerar_token() -> str:
|
|
return secrets.token_urlsafe(32)
|
|
|
|
|
|
def token_do_contexto(request: Request) -> str:
|
|
"""
|
|
Lê o token que o middleware colocou em request.state.csrf_token.
|
|
Retorna "" se o middleware não estiver montado (por exemplo, testes que
|
|
sobem só um router isolado, sem app/main.py) — nesse caso o formulário
|
|
simplesmente não terá csrf_token preenchido, e exigir_csrf abaixo vai
|
|
rejeitar o POST a menos que o teste monte o cookie/campo manualmente.
|
|
"""
|
|
return getattr(request.state, "csrf_token", "")
|
|
|
|
|
|
async def exigir_csrf(request: Request) -> None:
|
|
"""Dependency para toda rota POST que muda estado."""
|
|
form = await request.form()
|
|
token_formulario = str(form.get(CSRF_COOKIE_NAME, ""))
|
|
token_cookie = request.cookies.get(CSRF_COOKIE_NAME, "")
|
|
if not token_cookie or not token_formulario or not secrets.compare_digest(token_formulario, token_cookie):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Token CSRF ausente ou inválido.")
|