feat(frontends,security,api,ui): a frontend can hold a token scope of its own
This commit is contained in:
1 parent
60782d6276
commit
e79e9fc149
11 files changed
+245
-21
No files matched your search
@@ -21,7 +21,7 @@ from argon2.exceptions import InvalidHashError, VerifyMismatchError
|
||||
from beaver_gateway.storage import list_active_tokens, touch_token
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
from collections.abc import Iterable, Mapping
|
||||
|
||||
from beaver_gateway.storage import Database
|
||||
|
||||
@@ -35,8 +35,8 @@ class TokenStoreError(ValueError):
|
||||
"""Malformed ``BOOTSTRAP_TOKENS`` value or duplicate token."""
|
||||
|
||||
|
||||
VALID_SCOPES: frozenset[str] = frozenset({"*", "messages", "mcp", "admin", "api"})
|
||||
"""The scopes a ``Token.scope`` may hold.
|
||||
BUILTIN_SCOPES: frozenset[str] = frozenset({"*", "messages", "mcp", "admin", "api"})
|
||||
"""The scopes the gateway's own frontends define.
|
||||
|
||||
* ``*`` — wildcard, may use any frontend
|
||||
* ``messages`` — Anthropic Messages frontend only
|
||||
@@ -45,16 +45,24 @@ VALID_SCOPES: frozenset[str] = frozenset({"*", "messages", "mcp", "admin", "api"
|
||||
* ``admin`` — reserved for programmatic admin access; the AdminFrontend
|
||||
itself authenticates via session cookies, not bearer tokens, so this
|
||||
scope is unused today.
|
||||
|
||||
A setup's own frontend adds its scope on top; see :func:`scopes_with` and
|
||||
``GatewayRuntime.scopes``, which is what ``POST /api/tokens`` validates.
|
||||
"""
|
||||
|
||||
|
||||
def scopes_with(extra: Iterable[str | None]) -> frozenset[str]:
|
||||
"""The builtin scopes plus every frontend's own; ``None`` entries drop out."""
|
||||
return BUILTIN_SCOPES | {s for s in extra if s}
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class TokenIdentity:
|
||||
"""What :meth:`TokenStore.verify` resolves to on success.
|
||||
|
||||
``token_id`` is the DB row id for persisted tokens, or ``None`` for
|
||||
an env-bootstrap match (those have no DB row to touch). ``scope``
|
||||
gates which frontend the token may hit (see :data:`VALID_SCOPES`);
|
||||
gates which frontend the token may hit (see :data:`BUILTIN_SCOPES`);
|
||||
bootstrap tokens implicitly get ``"*"``.
|
||||
"""
|
||||
|
||||
@@ -67,8 +75,8 @@ class TokenIdentity:
|
||||
|
||||
``"*"`` is the wildcard; an exact match satisfies a single scope.
|
||||
Unknown ``required`` values intentionally fall through to a
|
||||
strict equality check — callers should pass one of
|
||||
:data:`VALID_SCOPES`.
|
||||
strict equality check — callers should pass a scope the runtime
|
||||
knows (:data:`BUILTIN_SCOPES` plus every frontend's own).
|
||||
"""
|
||||
return self.scope in ("*", required)
|
||||
|
||||
@@ -325,11 +333,12 @@ class TokenStore:
|
||||
|
||||
|
||||
__all__ = [
|
||||
"VALID_SCOPES",
|
||||
"BUILTIN_SCOPES",
|
||||
"TokenIdentity",
|
||||
"TokenStore",
|
||||
"TokenStoreError",
|
||||
"hash_token",
|
||||
"scopes_with",
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user