UDHOV's picture
Sync from GitHub via hub-sync
76a7925 verified
Raw
History Blame Contribute Delete
1.63 kB
"""
Session cookie utilities for HttpOnly cookie-based auth.
Uses HMAC-signed cookies so tenant_id is never exposed to JavaScript.
No additional dependencies required.
"""
from __future__ import annotations
import base64
import hmac
import json
import logging
import os
import secrets
import time
from typing import Any, cast
logger = logging.getLogger(__name__)
_SESSION_SECRET: str | None = None
_SESSION_TTL_S = 86400 # 24 hours
def _get_secret() -> str:
global _SESSION_SECRET
if _SESSION_SECRET is None:
_SESSION_SECRET = os.environ.get("SESSION_SECRET") or secrets.token_hex(32)
logger.info("Session secret initialized")
return _SESSION_SECRET
def create_session_token(tenant_id: str) -> str:
payload: dict[str, Any] = {
"t": tenant_id,
"e": int(time.time()) + _SESSION_TTL_S,
}
payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=")
sig = hmac.new(_get_secret().encode(), payload_b64.encode(), "sha256").hexdigest()[:16]
return f"{payload_b64}.{sig}"
def verify_session_token(token: str) -> str | None:
try:
payload_b64, sig = token.rsplit(".", 1)
expected = hmac.new(_get_secret().encode(), payload_b64.encode(), "sha256").hexdigest()[:16]
if not hmac.compare_digest(sig, expected):
return None
padded = payload_b64 + "=" * (4 - len(payload_b64) % 4)
data: dict[str, Any] = json.loads(base64.urlsafe_b64decode(padded))
if data["e"] < time.time():
return None
return cast(str, data["t"])
except Exception:
return None