"""Server-seitige Sitzungen: anlegen, prüfen, widerrufen. Im Cookie liegt nur das Klartext-Token; in der Datenbank nur dessen SHA-256-Hash. So lässt sich eine Sitzung serverseitig beenden (Logout, „überall abmelden"), und ein Blick in die DB verrät kein gültiges Cookie. """ from datetime import datetime, timedelta, timezone from sqlalchemy.orm import Session as DBSession from ..config import get_settings from ..models import Session as UserSession, User from ..security import generate_session_token, hash_session_token def _now() -> datetime: return datetime.now(timezone.utc) def _aware(dt: datetime) -> datetime: """SQLite gibt datetimes teils ohne tzinfo zurück – als UTC interpretieren.""" return dt if dt.tzinfo is not None else dt.replace(tzinfo=timezone.utc) def create_session(db: DBSession, user: User, user_agent: str | None = None) -> str: """Legt eine Sitzung an und gibt das Klartext-Token zurück (nur hier sichtbar).""" settings = get_settings() raw = generate_session_token() entry = UserSession( user_id=user.id, token_hash=hash_session_token(raw), expires_at=_now() + timedelta(days=settings.session_ttl_days), last_seen_at=_now(), user_agent=(user_agent or "")[:255] or None, ) db.add(entry) db.commit() return raw def validate(db: DBSession, raw_token: str) -> User | None: """Gibt den Benutzer zurück, wenn das Token gültig und nicht abgelaufen ist.""" if not raw_token: return None entry = ( db.query(UserSession) .filter(UserSession.token_hash == hash_session_token(raw_token)) .first() ) if entry is None: return None if _aware(entry.expires_at) < _now(): db.delete(entry) db.commit() return None user = db.get(User, entry.user_id) if user is None or not user.is_active: return None entry.last_seen_at = _now() db.commit() return user def revoke(db: DBSession, raw_token: str) -> None: entry = ( db.query(UserSession) .filter(UserSession.token_hash == hash_session_token(raw_token)) .first() ) if entry is not None: db.delete(entry) db.commit() def revoke_all(db: DBSession, user: User) -> None: db.query(UserSession).filter(UserSession.user_id == user.id).delete() db.commit()