feat: liga login por sessao, middleware de usuario e handler de NaoAutenticado

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Gustavo
2026-08-13 20:49:05 -03:00
co-authored by Claude Sonnet 5
parent 4a5c70861d
commit 09424e8afb
2 changed files with 38 additions and 5 deletions
+6
View File
@@ -3,6 +3,12 @@
ADMIN_USER=admin ADMIN_USER=admin
ADMIN_PASSWORD=SUBSTITUA_POR_UMA_SENHA_FORTE ADMIN_PASSWORD=SUBSTITUA_POR_UMA_SENHA_FORTE
# Chave usada para assinar o cookie de sessão de login (app/session.py).
# Sem isso definido, uma chave aleatória é gerada em memória a cada start
# do processo — funciona, mas todo restart derruba as sessões ativas
# (força login de novo). Em produção, defina um valor fixo e comprido.
SESSION_SECRET=SUBSTITUA_POR_UM_VALOR_ALEATORIO_COMPRIDO
# Banco de dev (mesmo container do projeto n8n / contatos-chave-web): # Banco de dev (mesmo container do projeto n8n / contatos-chave-web):
# DB_HOST=host.docker.internal (padrao — para rodar via docker-compose.yml, # DB_HOST=host.docker.internal (padrao — para rodar via docker-compose.yml,
# que executa este app dentro de um container) # que executa este app dentro de um container)
+32 -5
View File
@@ -1,24 +1,34 @@
""" """
Painel de Clientes — monta os routers de CLIENTE, CLIENTE_CONTATO e o Painel de Clientes — monta os routers de CLIENTE, CLIENTE_CONTATO, o
gerador de chave manual. Ver docs/superpowers/specs/2026-08-13-painel-clientes-web-design.md. gerador de chave manual, login/logoff, funcionários, minha conta e log.
Ver docs/superpowers/specs/2026-08-13-painel-clientes-web-design.md e
docs/superpowers/specs/2026-08-13-permissoes-login-sessao-design.md.
""" """
from fastapi import Depends, FastAPI, Request from fastapi import Depends, FastAPI, Request
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from .auth import exigir_login from . import session
from .auth import NaoAutenticado, UsuarioLogado, exigir_login
from .csrf import CSRF_COOKIE_NAME, gerar_token from .csrf import CSRF_COOKIE_NAME, gerar_token
from .routers import chave_manual, clientes, contatos, funcionarios, log from .routers import auth, chave_manual, clientes, contatos, funcionarios, log, minha_conta
app = FastAPI(title="Painel de Clientes", docs_url=None, redoc_url=None, openapi_url=None) app = FastAPI(title="Painel de Clientes", docs_url=None, redoc_url=None, openapi_url=None)
app.mount("/static", StaticFiles(directory="app/static"), name="static") app.mount("/static", StaticFiles(directory="app/static"), name="static")
app.include_router(auth.router)
app.include_router(clientes.router) app.include_router(clientes.router)
app.include_router(contatos.router) app.include_router(contatos.router)
app.include_router(chave_manual.router) app.include_router(chave_manual.router)
app.include_router(funcionarios.router) app.include_router(funcionarios.router)
app.include_router(minha_conta.router)
app.include_router(log.router) app.include_router(log.router)
@app.exception_handler(NaoAutenticado)
def _redirecionar_para_login(request: Request, exc: NaoAutenticado):
return RedirectResponse(url=f"/login?next={exc.caminho_original}", status_code=303)
@app.middleware("http") @app.middleware("http")
async def csrf_cookie_middleware(request: Request, call_next): async def csrf_cookie_middleware(request: Request, call_next):
""" """
@@ -40,6 +50,23 @@ async def csrf_cookie_middleware(request: Request, call_next):
return response return response
@app.middleware("http")
async def carregar_usuario_middleware(request: Request, call_next):
"""
Decodifica o cookie de sessão (se houver) e guarda o resultado em
request.state.usuario — só para os templates (base.html) decidirem o
que mostrar na navegação (ex: esconder "Funcionários" de quem não é
admin). NÃO é o mecanismo de proteção das rotas — isso continua sendo
Depends(exigir_login)/Depends(exigir_admin) em cada router.
"""
token = request.cookies.get(session.SESSION_COOKIE_NAME)
dados = session.ler_token(token)
request.state.usuario = (
UsuarioLogado(login=dados["login"], nome=dados["nome"], perfil=dados["perfil"]) if dados else None
)
return await call_next(request)
@app.get("/") @app.get("/")
def raiz(usuario: str = Depends(exigir_login)): def raiz(usuario: UsuarioLogado = Depends(exigir_login)):
return RedirectResponse(url="/clientes") return RedirectResponse(url="/clientes")