#!/usr/bin/env python3.14# -*- coding: utf-8 -*-"""In-memory web session table.A logged-in browser holds an opaque random token in a cookie; this maps thattoken to `(email, profile)` and an expiry. State is per-process by design: aservice restart logs everyone out, and remember-me tokens(:mod:`euler_site.auth.remember`) restore sessions across restarts."""from__future__importannotations__all__=['SessionStore']importsecretsimporttimefromeuler_site.auth.usersimportnormalize_emailclassSessionStore:"""Opaque-token → (email, profile, expiry) map with lazy expiry eviction."""def__init__(self,ttl_seconds:int)->None:self._ttl=ttl_secondsself._sessions:dict[str,tuple[str,str,float]]={}defcreate(self,email:str,profile:str)->str:"""Open a session; return the fresh opaque cookie token."""token=secrets.token_urlsafe(32)self._sessions[token]=(normalize_email(email),profile,time.time()+self._ttl)returntokendefget(self,token:str|None)->tuple[str,str]|None:"""Return `(email, profile)` for a live token, or None (evicting expired)."""ifnottoken:returnNoneentry=self._sessions.get(token)ifentryisNone:returnNoneemail,profile,expiry=entryiftime.time()>=expiry:self._sessions.pop(token,None)returnNonereturnemail,profiledefdrop(self,token:str|None)->None:"""Close the session for *token* (logout)."""iftoken:self._sessions.pop(token,None)defrevoke_all(self)->int:"""Close every session (`logout-all`); return how many."""count=len(self._sessions)self._sessions.clear()returncountdefrevoke_email(self,email:str,keep:str|None=None)->int:"""Close every session for *email* (disable/remove); return how many. *keep* spares one token: the signed-in session that performed a self-service password change stays live while every other device logs out (the disable/remove callers pass no *keep* — full revoke). """key_email=normalize_email(email)doomed=[tokenfortoken,(owner,_,_)inself._sessions.items()ifowner==key_emailandtoken!=keep]fortokenindoomed:delself._sessions[token]returnlen(doomed)