Files
ssh-jumphost/app/security/sessions.py
2026-08-19 22:33:19 +02:00

71 lines
1.9 KiB
Python

"""
Signierte, serverseitig invalidierbare Session-Cookies.
Kein separates Session-Store noetig: Das Cookie traegt user_id,
session_version (fuer harte Invalidierung, z.B. bei Passwortwechsel) und
zwei Zeitstempel (Login-Zeit fuer den absoluten Timeout, Last-Seen fuer den
gleitenden Idle-Timeout). Signatur ueber einen vom KEK getrennten Secret
(Schluesseltrennung, Konzept 6.2).
"""
from __future__ import annotations
import time
from dataclasses import dataclass
from itsdangerous import BadSignature, URLSafeSerializer
from app.config import settings
_serializer = URLSafeSerializer(settings.session_secret.hex(), salt="jumphost-session")
SESSION_COOKIE_NAME = "jh_session"
@dataclass
class SessionPayload:
user_id: int
session_version: int
login_ts: float
last_seen_ts: float
def create_session_token(user_id: int, session_version: int) -> str:
now = time.time()
payload = {"uid": user_id, "sv": session_version, "iat": now, "seen": now}
return _serializer.dumps(payload)
def refresh_session_token(payload: SessionPayload) -> str:
data = {
"uid": payload.user_id,
"sv": payload.session_version,
"iat": payload.login_ts,
"seen": time.time(),
}
return _serializer.dumps(data)
def decode_session_token(token: str) -> SessionPayload | None:
try:
data = _serializer.loads(token)
except BadSignature:
return None
try:
return SessionPayload(
user_id=int(data["uid"]),
session_version=int(data["sv"]),
login_ts=float(data["iat"]),
last_seen_ts=float(data["seen"]),
)
except (KeyError, TypeError, ValueError):
return None
def is_expired(payload: SessionPayload) -> bool:
now = time.time()
if now - payload.last_seen_ts > settings.session_idle_timeout_s:
return True
if now - payload.login_ts > settings.session_absolute_timeout_s:
return True
return False