mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-06 20:43:46 +08:00
00364fd645
Separate Galaxy authentication and local access policy from web presentation. Co-authored-by: Danny <dannyirwin31@yahoo.com> Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu> Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com> Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
64 lines
2.1 KiB
Python
64 lines
2.1 KiB
Python
"""Bounded, process-local browser sessions for loopback Galaxy diagnostics."""
|
|
|
|
import hashlib
|
|
import hmac
|
|
import secrets
|
|
import time
|
|
from collections.abc import Callable
|
|
|
|
|
|
class LocalSessions:
|
|
LIFETIME = 30 * 60
|
|
MAX_SESSIONS = 64
|
|
MAX_FAILURES = 5
|
|
FAILURE_PAUSE = 30
|
|
|
|
def __init__(self, clock: Callable[[], float] = time.monotonic):
|
|
self.clock = clock
|
|
self._sessions: dict[bytes, tuple[float, bytes]] = {}
|
|
self._failures = 0
|
|
self._blocked_until = 0.0
|
|
|
|
@staticmethod
|
|
def _key(token: str) -> bytes:
|
|
return hashlib.sha256(token.encode('ascii')).digest()
|
|
|
|
def _expire(self) -> None:
|
|
now = self.clock()
|
|
self._sessions = {key: value for key, value in self._sessions.items() if value[0] > now}
|
|
|
|
def throttled(self) -> bool:
|
|
if self._failures >= self.MAX_FAILURES and self.clock() < self._blocked_until:
|
|
return True
|
|
if self._blocked_until > 0 and self.clock() >= self._blocked_until:
|
|
self._failures = 0
|
|
self._blocked_until = 0.0
|
|
return False
|
|
|
|
def failed_login(self) -> None:
|
|
self._failures += 1
|
|
if self._failures >= self.MAX_FAILURES:
|
|
self._blocked_until = self.clock() + self.FAILURE_PAUSE
|
|
|
|
def create(self, generation: bytes, *, reset_failures: bool = True) -> str:
|
|
self._expire()
|
|
if len(self._sessions) >= self.MAX_SESSIONS:
|
|
oldest = min(self._sessions, key=lambda key: self._sessions[key][0])
|
|
self._sessions.pop(oldest)
|
|
token = secrets.token_urlsafe(32)
|
|
self._sessions[self._key(token)] = (self.clock() + self.LIFETIME, generation)
|
|
if reset_failures:
|
|
self._failures = 0
|
|
return token
|
|
|
|
def valid(self, token: str | None, generation: bytes | None) -> bool:
|
|
if token is None or generation is None or len(token) > 128 or not token.isascii():
|
|
return False
|
|
self._expire()
|
|
session = self._sessions.get(self._key(token))
|
|
return bool(session is not None and hmac.compare_digest(session[1], generation))
|
|
|
|
def revoke(self, token: str | None) -> None:
|
|
if token is not None and len(token) <= 128 and token.isascii():
|
|
self._sessions.pop(self._key(token), None)
|