second commit
This commit is contained in:
70
app/security/sessions.py
Normal file
70
app/security/sessions.py
Normal file
@ -0,0 +1,70 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user