Override Actor auth for easier dev testing (#33)

In this PR:
- The Actor health check route now _never_ requires auth (bearer token).
It is always unprotected.
- `arcade dev --no-auth` disables all Actor auth entirely, making all
routes unprotected. Useful for debugging, but emits a warning to the
console.
This commit is contained in:
Nate Barbettini 2024-09-10 09:39:12 -07:00 committed by GitHub
parent 75c6a2becf
commit c59bb678dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 42 additions and 13 deletions

View file

@ -32,11 +32,12 @@ class BaseActor(Actor):
HealthCheckComponent,
)
def __init__(self) -> None:
def __init__(self, disable_auth: bool = False) -> None:
"""
Initialize the BaseActor with an empty ToolCatalog.
"""
self.catalog = ToolCatalog()
self.disable_auth = disable_auth
def get_catalog(self) -> list[ToolDefinition]:
"""

View file

@ -27,7 +27,9 @@ class Router(ABC):
"""
@abstractmethod
def add_route(self, endpoint_path: str, handler: Callable, method: str) -> None:
def add_route(
self, endpoint_path: str, handler: Callable, method: str, require_auth: bool = True
) -> None:
"""
Add a route to the router.
"""

View file

@ -48,7 +48,7 @@ class HealthCheckComponent(ActorComponent):
"""
Register the health check route with the router.
"""
router.add_route("health", self, method="GET")
router.add_route("health", self, method="GET", require_auth=False)
async def __call__(self, request: RequestData) -> dict[str, Any]:
"""

View file

@ -17,12 +17,12 @@ class FastAPIActor(BaseActor):
An Arcade Actor that is hosted inside a FastAPI app.
"""
def __init__(self, app: FastAPI) -> None:
def __init__(self, app: FastAPI, *, disable_auth: bool = False) -> None:
"""
Initialize the FastAPIActor with a FastAPI app
instance and an empty ToolCatalog.
"""
super().__init__()
super().__init__(disable_auth)
self.app = app
self.router = FastAPIRouter(app, self)
self.register_routes(self.router)
@ -33,14 +33,16 @@ class FastAPIRouter(Router):
self.app = app
self.actor = actor
def _wrap_handler(self, handler: Callable) -> Callable:
def _wrap_handler(self, handler: Callable, require_auth: bool = True) -> Callable:
"""
Wrap the handler to handle FastAPI-specific request and response.
"""
use_auth_for_route = not self.actor.disable_auth and require_auth
async def wrapped_handler(
request: Request,
_: None = Depends(validate_engine_request),
_: None = Depends(validate_engine_request) if use_auth_for_route else None,
) -> Any:
body_str = await request.body()
body_json = json.loads(body_str) if body_str else {}
@ -56,10 +58,14 @@ class FastAPIRouter(Router):
return wrapped_handler
def add_route(self, endpoint_path: str, handler: Callable, method: str) -> None:
def add_route(
self, endpoint_path: str, handler: Callable, method: str, require_auth: bool = True
) -> None:
"""
Add a route to the FastAPI application.
"""
self.app.add_api_route(
f"{self.actor.base_path}/{endpoint_path}", self._wrap_handler(handler), methods=[method]
f"{self.actor.base_path}/{endpoint_path}",
self._wrap_handler(handler, require_auth),
methods=[method],
)

View file

@ -56,10 +56,15 @@ class FlaskRouter(Router):
return wrapped_handler
def add_route(self, endpoint_path: str, handler: Callable, method: str) -> None:
def add_route(
self, endpoint_path: str, handler: Callable, method: str, require_auth: bool = True
) -> None:
"""
Add a route to the Flask application.
"""
# TODO: Implement auth
# use_auth_for_route = not self.actor.disable_auth and require_auth
handler_name = handler.__name__ if hasattr(handler, "__name__") else type(handler).__name__
endpoint_name = f"actor_{handler_name}_{method}"
self.app.add_url_rule(

View file

@ -263,15 +263,28 @@ def dev(
port: int = typer.Option(
"8000", "-p", "--port", help="Port for the app, defaults to ", show_default=True
),
disable_auth: bool = typer.Option(
False,
"--no-auth",
help="Disable authentication for the actor. Not recommended for production.",
show_default=True,
),
) -> None:
"""
Starts the actor with host, port, and reload options. Uses
Uvicorn as ASGI actor. Parameters allow runtime configuration.
"""
if disable_auth:
console.print(
"⚠️ Actor authentication is disabled. Not recommended for production.",
style="bold yellow",
)
from arcade.cli.serve import serve_default_actor
try:
serve_default_actor(host, port)
serve_default_actor(host, port, disable_auth)
except KeyboardInterrupt:
console.print("actor stopped by user.", style="bold red")
typer.Exit()

View file

@ -18,7 +18,9 @@ from arcade.core.toolkit import Toolkit
console = Console()
def serve_default_actor(host: str = "127.0.0.1", port: int = 8000) -> None:
def serve_default_actor(
host: str = "127.0.0.1", port: int = 8000, disable_auth: bool = False
) -> None:
"""
Get an instance of a FastAPI server with the Arcade Actor.
"""
@ -36,7 +38,7 @@ def serve_default_actor(host: str = "127.0.0.1", port: int = 8000) -> None:
description="Arcade AI default Actor implementation using FastAPI.",
version="0.1.0",
)
actor = FastAPIActor(app)
actor = FastAPIActor(app, disable_auth=disable_auth)
for toolkit in toolkits:
actor.register_toolkit(toolkit)