Files
gti d95165fc8f Add 'gerenciador/' from commit '34cc9c75a57dedbf61a6e6a1ba1f35f76e72434b'
git-subtree-dir: gerenciador
git-subtree-mainline: f4f546c928
git-subtree-split: 34cc9c75a5
2026-09-29 14:43:12 -03:00

69 lines
2.4 KiB
Python

"""
Token de sessão de login — cookie "painel_session" (ver app/auth.py e
app/routers/auth.py). Stateless: nada gravado no banco, a validade inteira
depende da assinatura HMAC-SHA256 com SESSION_SECRET (app/main.py define a
variável de ambiente; se ausente, um valor aleatório é gerado em memória no
start do processo — reiniciar sem SESSION_SECRET fixo derruba as sessões
ativas, forçando login de novo, efeito colateral aceito).
Formato do token: "<payload em base64url>.<assinatura HMAC em hex>"
Payload (antes de codificar): JSON com login, nome, perfil e validade
("exp", timestamp Unix).
Nunca lança exceção para entrada inválida — ler_token() sempre devolve
None nesse caso, para o chamador (app/auth.py) tratar como "não logado".
"""
import base64
import hashlib
import hmac
import json
import os
import secrets
import time
SESSION_COOKIE_NAME = "painel_session"
VALIDADE_SEGUNDOS = 12 * 60 * 60 # 12 horas
_SECRET_EM_MEMORIA = secrets.token_bytes(32)
def _chave_secreta() -> bytes:
configurada = os.environ.get("SESSION_SECRET", "")
if configurada:
return configurada.encode("utf-8")
return _SECRET_EM_MEMORIA
def _assinar(payload_b64: bytes) -> str:
return hmac.new(_chave_secreta(), payload_b64, hashlib.sha256).hexdigest()
def criar_token(login: str, nome: str, perfil: str, agora: float | None = None) -> str:
momento = agora if agora is not None else time.time()
payload = {"login": login, "nome": nome, "perfil": perfil, "exp": momento + VALIDADE_SEGUNDOS}
payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode("utf-8"))
assinatura = _assinar(payload_b64)
return payload_b64.decode("ascii") + "." + assinatura
def ler_token(token: str | None, agora: float | None = None) -> dict | None:
if not token or "." not in token:
return None
payload_b64_str, assinatura_recebida = token.rsplit(".", 1)
payload_b64 = payload_b64_str.encode("ascii")
assinatura_esperada = _assinar(payload_b64)
if not hmac.compare_digest(assinatura_recebida, assinatura_esperada):
return None
try:
payload = json.loads(base64.urlsafe_b64decode(payload_b64))
except (ValueError, UnicodeDecodeError):
return None
momento = agora if agora is not None else time.time()
if momento > payload.get("exp", 0):
return None
return {"login": payload["login"], "nome": payload["nome"], "perfil": payload["perfil"]}