69 lines
2.4 KiB
Python
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"]}
|