feat: add audit, throttle and session foundations
Audit entries written only inside the caller's transaction, database throttle counters that refuse to run inside a transaction that could roll back, and a database session store with the user key in its own column, end_sessions and idle and absolute limits. Ticket 1.3. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sk72AxLwQnYttF6VFSxQLt
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
003ce6e0c2
commit
73505ac717
@@ -0,0 +1,52 @@
|
||||
"""Session user and the only way to end sessions (AD-3, AD-6)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .models import UserSession
|
||||
from .session_store import CREATED_AT, USER_KEY, USER_SAM
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SessionUser:
|
||||
key: str # lower-case, used for every database key
|
||||
sam: str # exact sAMAccountName from AD
|
||||
|
||||
@classmethod
|
||||
def from_sam(cls, sam: str) -> SessionUser:
|
||||
return cls(key=sam.lower(), sam=sam)
|
||||
|
||||
|
||||
def start_session(request, sam: str) -> SessionUser:
|
||||
"""Sign a user in on this request; the session key is rotated and old data dropped."""
|
||||
user = SessionUser.from_sam(sam)
|
||||
request.session.flush()
|
||||
request.session[USER_KEY] = user.key
|
||||
request.session[USER_SAM] = user.sam
|
||||
request.session[CREATED_AT] = time.time()
|
||||
return user
|
||||
|
||||
|
||||
def current_user(request) -> SessionUser | None:
|
||||
session = getattr(request, "session", None)
|
||||
if session is None:
|
||||
return None
|
||||
key = session.get(USER_KEY)
|
||||
sam = session.get(USER_SAM)
|
||||
if not key or not sam:
|
||||
return None
|
||||
return SessionUser(key=key, sam=sam)
|
||||
|
||||
|
||||
def end_sessions(user: SessionUser | str, except_session_key: str | None = None) -> int:
|
||||
"""Delete every session of one user, optionally keeping one. Returns how many were ended."""
|
||||
key = (user.key if isinstance(user, SessionUser) else str(user)).lower()
|
||||
if not key:
|
||||
return 0
|
||||
qs = UserSession.objects.filter(username=key)
|
||||
if except_session_key:
|
||||
qs = qs.exclude(session_key=except_session_key)
|
||||
deleted, _ = qs.delete()
|
||||
return deleted
|
||||
Reference in New Issue
Block a user