""" 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 (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"]}