deer-flow/backend/app/plugins/auth/injection/registry_loader.py
rayhpeng 0f82f8a3a2 feat(app): add plugin system with auth plugin and static assets
Add new application structure:
- app/main.py - application entry point
- app/plugins/ - plugin system with auth plugin:
  - api/ - REST API endpoints and schemas
  - authorization/ - auth policies, providers, hooks
  - domain/ - business logic (service, models, jwt, password)
  - injection/ - route injection and guards
  - ops/ - operational utilities
  - runtime/ - runtime configuration
  - security/ - middleware, CSRF, dependencies
  - storage/ - user repositories and models
- app/static/ - static assets (scalar.js for API docs)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-22 11:31:42 +08:00

113 lines
3.5 KiB
Python

"""Load auth route policies from the plugin's YAML registry."""
from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
from starlette.routing import compile_path
import yaml
_POLICY_FILE = Path(__file__).resolve().parents[1] / "route_policies.yaml"
@dataclass(frozen=True)
class RoutePolicySpec:
public: bool = False
capability: str | None = None
policies: tuple[str, ...] = ()
require_existing: bool = True
@dataclass(frozen=True)
class RoutePolicyEntry:
method: str
path: str
spec: RoutePolicySpec
path_regex: object = field(repr=False)
def matches_request(self, method: str, path: str) -> bool:
if self.method != method.upper():
return False
return self.path_regex.match(path) is not None
class RoutePolicyRegistry:
def __init__(self, entries: list[RoutePolicyEntry]) -> None:
self._entries = entries
self._specs = {(entry.method, entry.path): entry.spec for entry in entries}
def get(self, method: str, path_template: str) -> RoutePolicySpec | None:
return self._specs.get((method.upper(), path_template))
def has(self, method: str, path_template: str) -> bool:
return (method.upper(), path_template) in self._specs
def match_request(self, method: str, path: str) -> RoutePolicySpec | None:
normalized_method = method.upper()
for entry in self._entries:
if entry.matches_request(normalized_method, path):
return entry.spec
return None
def is_public_request(self, method: str, path: str) -> bool:
spec = self.match_request(method, path)
return bool(spec and spec.public)
@property
def keys(self) -> set[tuple[str, str]]:
return set(self._specs)
def _normalize_methods(item: dict) -> tuple[str, ...]:
methods = item.get("methods")
if methods is None:
methods = [item["method"]]
if isinstance(methods, str):
methods = [methods]
return tuple(str(method).upper() for method in methods)
def _build_spec(item: dict) -> RoutePolicySpec:
return RoutePolicySpec(
public=bool(item.get("public", False)),
capability=item.get("capability"),
policies=tuple(item.get("policies", [])),
require_existing=bool(item.get("require_existing", True)),
)
def load_route_policy_registry() -> RoutePolicyRegistry:
payload = yaml.safe_load(_POLICY_FILE.read_text(encoding="utf-8")) or {}
raw_routes: list[dict] = []
for section, entries in payload.items():
if section == "routes":
if isinstance(entries, list):
raw_routes.extend(entries)
continue
if not isinstance(entries, list):
continue
for item in entries:
normalized = dict(item)
if section == "public":
normalized["public"] = True
raw_routes.append(normalized)
entries: list[RoutePolicyEntry] = []
for item in raw_routes:
path = str(item["path"])
spec = _build_spec(item)
path_regex, _, _ = compile_path(path)
for method in _normalize_methods(item):
entries.append(
RoutePolicyEntry(
method=method,
path=path,
spec=spec,
path_regex=path_regex,
)
)
return RoutePolicyRegistry(entries)
__all__ = ["RoutePolicyRegistry", "RoutePolicySpec", "load_route_policy_registry"]