Update
This commit is contained in:
@@ -9,6 +9,8 @@ import threading
|
||||
import time
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, cast
|
||||
from uuid import UUID
|
||||
|
||||
from app.core.config import Settings
|
||||
|
||||
@@ -17,6 +19,8 @@ from app.core.config import Settings
|
||||
class AuthPrincipal:
|
||||
username: str
|
||||
expires_at: int
|
||||
role: Literal["operator", "guest"] = "operator"
|
||||
project_id: UUID | None = None
|
||||
|
||||
|
||||
class AuthService:
|
||||
@@ -93,15 +97,32 @@ class AuthService:
|
||||
return username_matches and password_matches
|
||||
|
||||
@classmethod
|
||||
def create_session_token(cls, username: str, settings: Settings, *, now: int | None = None) -> str:
|
||||
def create_session_token(
|
||||
cls,
|
||||
username: str,
|
||||
settings: Settings,
|
||||
*,
|
||||
role: Literal["operator", "guest"] = "operator",
|
||||
project_id: UUID | None = None,
|
||||
ttl_seconds: int | None = None,
|
||||
now: int | None = None,
|
||||
) -> str:
|
||||
issued_at = int(time.time() if now is None else now)
|
||||
if role == "guest" and project_id is None:
|
||||
raise ValueError("Guest sessions must be scoped to a demo project")
|
||||
resolved_ttl = ttl_seconds if ttl_seconds is not None else (
|
||||
settings.guest_session_ttl_seconds if role == "guest" else settings.auth_session_ttl_seconds
|
||||
)
|
||||
payload = {
|
||||
"exp": issued_at + settings.auth_session_ttl_seconds,
|
||||
"exp": issued_at + resolved_ttl,
|
||||
"iat": issued_at,
|
||||
"jti": secrets.token_urlsafe(12),
|
||||
"role": role,
|
||||
"sub": username,
|
||||
"v": 1,
|
||||
"v": 2,
|
||||
}
|
||||
if project_id is not None:
|
||||
payload["project_id"] = str(project_id)
|
||||
encoded_payload = cls._b64_encode(
|
||||
json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")
|
||||
)
|
||||
@@ -136,14 +157,35 @@ class AuthService:
|
||||
username = str(payload.get("sub") or "")
|
||||
expires_at = int(payload.get("exp") or 0)
|
||||
issued_at = int(payload.get("iat") or 0)
|
||||
version = int(payload.get("v") or 0)
|
||||
role_value = str(payload.get("role") or "operator")
|
||||
current = int(time.time() if now is None else now)
|
||||
if payload.get("v") != 1 or username != settings.auth_username:
|
||||
if version not in {1, 2} or role_value not in {"operator", "guest"}:
|
||||
return None
|
||||
role = cast(Literal["operator", "guest"], role_value)
|
||||
if issued_at <= 0 or issued_at > current + 60 or expires_at <= current:
|
||||
return None
|
||||
if expires_at - issued_at > settings.auth_session_ttl_seconds:
|
||||
if role == "operator":
|
||||
if username != settings.auth_username:
|
||||
return None
|
||||
max_ttl = settings.auth_session_ttl_seconds
|
||||
project_id = None
|
||||
else:
|
||||
if not settings.guest_access_enabled or username != settings.guest_display_name:
|
||||
return None
|
||||
max_ttl = settings.guest_session_ttl_seconds
|
||||
raw_project_id = payload.get("project_id")
|
||||
if not raw_project_id:
|
||||
return None
|
||||
project_id = UUID(str(raw_project_id))
|
||||
if expires_at - issued_at > max_ttl:
|
||||
return None
|
||||
return AuthPrincipal(username=username, expires_at=expires_at)
|
||||
return AuthPrincipal(
|
||||
username=username,
|
||||
expires_at=expires_at,
|
||||
role=role,
|
||||
project_id=project_id,
|
||||
)
|
||||
except (TypeError, ValueError, json.JSONDecodeError, UnicodeDecodeError):
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user