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
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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user