Files
GTI_GERENCIADOR_FISCAL/app/routers/chave_manual.py
T
GustavoandClaude Sonnet 5 5c577a644a fix(seguranca): move algoritmo de geracao de chave do JS pro backend
O algoritmo (CHAVE_INTERNA + funcoes gerarReativacao/gerarInstalacao)
ficava 100% legivel no codigo-fonte da pagina — qualquer pessoa com
acesso ao painel conseguia abrir o DevTools e extrair a logica pra
gerar chaves de licenca fora do sistema.

Move tudo pra app/gerador_chave.py (roda so no servidor) e expoe via
POST /gerador-chave/gerar (autenticado, protegido por CSRF). A pagina
agora so manda os dados do formulario e mostra o resultado que volta
do backend. Algoritmo verificado byte-a-byte contra os 3 exemplos
reais conhecidos (cliente 000001, ALUGUEL). Bonus: o byte inicial
agora usa secrets.randbelow (CSPRNG) em vez de Math.random() do JS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 10:03:19 -03:00

61 lines
2.4 KiB
Python

"""
Calculadora de chave de reativação/instalação, para uso manual (reenvio/
correção pontual). O algoritmo em si (app/gerador_chave.py) roda só aqui
no backend — a página não recebe mais nada além do resultado já pronto,
pra ninguém conseguir extrair o algoritmo pelo código-fonte da página.
NUNCA grava em Clientes_Chaves.
"""
import re
from fastapi import APIRouter, Depends, Form, Request
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from ..auth import exigir_login
from ..csrf import exigir_csrf, token_do_contexto
from ..gerador_chave import gerar_instalacao, gerar_reativacao
router = APIRouter()
templates = Jinja2Templates(directory="app/templates")
_RE_CODIGO = re.compile(r"^[0-9]{1,6}$")
_RE_REFERENCIA = re.compile(r"^\d{4}-\d{2}$")
@router.get("/gerador-chave", response_class=HTMLResponse)
def pagina(request: Request, usuario: str = Depends(exigir_login)):
return templates.TemplateResponse(request, "gerador_chave.html", {"csrf_token": token_do_contexto(request)})
@router.post("/gerador-chave/gerar", dependencies=[Depends(exigir_csrf)])
def gerar(
tipo: str = Form(...),
codigo: str = Form(...),
referencia: str = Form(""),
modalidade: str = Form(""),
usuario: str = Depends(exigir_login),
):
codigo = codigo.strip()
if not _RE_CODIGO.match(codigo):
return JSONResponse({"erro": "Código do cliente deve ter até 6 dígitos numéricos."}, status_code=400)
codigo_formatado = codigo.zfill(6)
if tipo == "reativacao":
if not _RE_REFERENCIA.match(referencia):
return JSONResponse({"erro": "Selecione o mês/ano de referência."}, status_code=400)
ano_str, mes_str = referencia.split("-")
mes, ano = int(mes_str), int(ano_str)
if not (1 <= mes <= 12):
return JSONResponse({"erro": "Mês fora do intervalo 1-12."}, status_code=400)
if not (2000 <= ano <= 2100):
return JSONResponse({"erro": "Ano inválido (use entre 2000 e 2100)."}, status_code=400)
chave = gerar_reativacao(codigo_formatado, mes, ano)
elif tipo == "instalacao":
if modalidade not in ("ALUGUEL", "COMPRA"):
return JSONResponse({"erro": "Modalidade inválida."}, status_code=400)
chave = gerar_instalacao(codigo_formatado, modalidade)
else:
return JSONResponse({"erro": "Tipo de chave inválido."}, status_code=400)
return JSONResponse({"chave": chave})