Initial commit: SHARED library with LFS for binary assets
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger("relay-gateway.keys")
|
||||
|
||||
LEVELS = ("all", "sre", "sqb", "tss")
|
||||
CHANNELS_FOR_LEVEL: dict[str, tuple[str, ...]] = {
|
||||
"all": ("sre", "tss"),
|
||||
"sre": ("sre",),
|
||||
"sqb": ("sre",), # legacy alias
|
||||
"tss": ("tss",),
|
||||
}
|
||||
|
||||
|
||||
def hash_token(token: str) -> str:
|
||||
return hashlib.sha256(token.strip().encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Grant:
|
||||
name: str
|
||||
level: str
|
||||
channels: tuple[str, ...]
|
||||
|
||||
|
||||
def can_access(grant: Grant, channel: str) -> bool:
|
||||
return channel in grant.channels
|
||||
|
||||
|
||||
class KeyStore:
|
||||
"""Loads relay_keys.json (hashed tokens) and reloads on mtime change."""
|
||||
|
||||
def __init__(self, path: Path):
|
||||
self._path = Path(path)
|
||||
self._mtime: float | None = None
|
||||
self._by_hash: dict[str, Grant] = {}
|
||||
self._load()
|
||||
|
||||
def _load(self) -> None:
|
||||
try:
|
||||
stat = self._path.stat()
|
||||
except FileNotFoundError:
|
||||
self._by_hash = {}
|
||||
self._mtime = None
|
||||
return
|
||||
if self._mtime is not None and stat.st_mtime == self._mtime:
|
||||
return
|
||||
try:
|
||||
raw = json.loads(self._path.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError) as exc:
|
||||
logger.warning("Failed to read key store: %s", exc)
|
||||
return
|
||||
parsed: dict[str, Grant] = {}
|
||||
for token_hash, meta in (raw or {}).items():
|
||||
level = str(meta.get("level", "")).strip()
|
||||
if level not in CHANNELS_FOR_LEVEL:
|
||||
logger.warning("Skipping key %s: bad level %r", meta.get("name"), level)
|
||||
continue
|
||||
parsed[token_hash] = Grant(
|
||||
name=str(meta.get("name", "unnamed")),
|
||||
level=level,
|
||||
channels=CHANNELS_FOR_LEVEL[level],
|
||||
)
|
||||
self._by_hash = parsed
|
||||
self._mtime = stat.st_mtime
|
||||
|
||||
def resolve(self, token: str) -> Grant | None:
|
||||
self._load()
|
||||
if not token:
|
||||
return None
|
||||
return self._by_hash.get(hash_token(token))
|
||||
Reference in New Issue
Block a user