Prepare GeoIntel for public release
Managed validation / Managed repository validation (pull_request) Successful in 1m46s
GeoIntel release gates / Compile, test, contracts and builds (pull_request) Successful in 1m51s
GeoIntel release gates / Python and npm vulnerability policy (pull_request) Successful in 20s
GeoIntel release gates / Production AI image, SBOM and container scan (pull_request) Successful in 15m3s
GeoIntel release gates / Deploy exact gated revision to Unraid (pull_request) Skipped

This commit is contained in:
Jens
2026-08-31 21:33:10 +02:00
parent dcd67b11d1
commit 2cdf9c99c6
195 changed files with 595 additions and 207595 deletions
+45 -2
View File
@@ -8,17 +8,19 @@ import secrets
import threading
import time
from collections import deque
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import Literal, cast
from uuid import UUID
from app.core.config import Settings
from app.core.public_demo import PUBLIC_DEMO_PROJECT_ID
@dataclass(frozen=True)
class AuthPrincipal:
username: str
expires_at: int
session_id: str = field(default_factory=lambda: secrets.token_urlsafe(12))
role: Literal["operator", "guest"] = "operator"
project_id: UUID | None = None
@@ -30,6 +32,10 @@ class AuthService:
FAILURE_WINDOW_SECONDS = 300
_failures: dict[str, deque[float]] = {}
_failure_lock = threading.Lock()
_guest_requests: dict[str, deque[float]] = {}
_guest_request_lock = threading.Lock()
_active_guest_compute = 0
_guest_compute_lock = threading.Lock()
@staticmethod
def _b64_encode(value: bytes) -> str:
@@ -159,8 +165,9 @@ class AuthService:
issued_at = int(payload.get("iat") or 0)
version = int(payload.get("v") or 0)
role_value = str(payload.get("role") or "operator")
session_id = str(payload.get("jti") or "")
current = int(time.time() if now is None else now)
if version not in {1, 2} or role_value not in {"operator", "guest"}:
if version not in {1, 2} or role_value not in {"operator", "guest"} or not session_id:
return None
role = cast(Literal["operator", "guest"], role_value)
if issued_at <= 0 or issued_at > current + 60 or expires_at <= current:
@@ -178,11 +185,14 @@ class AuthService:
if not raw_project_id:
return None
project_id = UUID(str(raw_project_id))
if project_id != PUBLIC_DEMO_PROJECT_ID:
return None
if expires_at - issued_at > max_ttl:
return None
return AuthPrincipal(
username=username,
expires_at=expires_at,
session_id=session_id,
role=role,
project_id=project_id,
)
@@ -215,3 +225,36 @@ class AuthService:
def clear_failures(cls, key: str) -> None:
with cls._failure_lock:
cls._failures.pop(key, None)
@classmethod
def consume_guest_request(
cls,
key: str,
*,
max_requests: int,
window_seconds: int = 60,
now: float | None = None,
) -> int:
"""Record a guest action and return Retry-After seconds when limited."""
current = time.monotonic() if now is None else now
with cls._guest_request_lock:
attempts = cls._guest_requests.setdefault(key, deque())
while attempts and current - attempts[0] >= window_seconds:
attempts.popleft()
if len(attempts) >= max_requests:
return max(1, int(window_seconds - (current - attempts[0])))
attempts.append(current)
return 0
@classmethod
def try_acquire_guest_compute(cls, *, max_concurrency: int) -> bool:
with cls._guest_compute_lock:
if cls._active_guest_compute >= max_concurrency:
return False
cls._active_guest_compute += 1
return True
@classmethod
def release_guest_compute(cls) -> None:
with cls._guest_compute_lock:
cls._active_guest_compute = max(0, cls._active_guest_compute - 1)