From c59bb678dd118aae61f269759b72845b94221541 Mon Sep 17 00:00:00 2001 From: Nate Barbettini Date: Tue, 10 Sep 2024 09:39:12 -0700 Subject: [PATCH] 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. --- arcade/arcade/actor/core/base.py | 3 ++- arcade/arcade/actor/core/common.py | 4 +++- arcade/arcade/actor/core/components.py | 2 +- arcade/arcade/actor/fastapi/actor.py | 18 ++++++++++++------ arcade/arcade/actor/flask/actor.py | 7 ++++++- arcade/arcade/cli/main.py | 15 ++++++++++++++- arcade/arcade/cli/serve.py | 6 ++++-- 7 files changed, 42 insertions(+), 13 deletions(-) diff --git a/arcade/arcade/actor/core/base.py b/arcade/arcade/actor/core/base.py index 965eb656..4b5e6313 100644 --- a/arcade/arcade/actor/core/base.py +++ b/arcade/arcade/actor/core/base.py @@ -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]: """ diff --git a/arcade/arcade/actor/core/common.py b/arcade/arcade/actor/core/common.py index 45410bb3..4bcb6657 100644 --- a/arcade/arcade/actor/core/common.py +++ b/arcade/arcade/actor/core/common.py @@ -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. """ diff --git a/arcade/arcade/actor/core/components.py b/arcade/arcade/actor/core/components.py index ba5520a0..1b7864ed 100644 --- a/arcade/arcade/actor/core/components.py +++ b/arcade/arcade/actor/core/components.py @@ -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]: """ diff --git a/arcade/arcade/actor/fastapi/actor.py b/arcade/arcade/actor/fastapi/actor.py index 1b310c89..5bb9621c 100644 --- a/arcade/arcade/actor/fastapi/actor.py +++ b/arcade/arcade/actor/fastapi/actor.py @@ -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], ) diff --git a/arcade/arcade/actor/flask/actor.py b/arcade/arcade/actor/flask/actor.py index 16e3dbbc..c02f9428 100644 --- a/arcade/arcade/actor/flask/actor.py +++ b/arcade/arcade/actor/flask/actor.py @@ -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( diff --git a/arcade/arcade/cli/main.py b/arcade/arcade/cli/main.py index ba2e8202..f791fa25 100644 --- a/arcade/arcade/cli/main.py +++ b/arcade/arcade/cli/main.py @@ -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() diff --git a/arcade/arcade/cli/serve.py b/arcade/arcade/cli/serve.py index 03d7cbdc..5a669a98 100644 --- a/arcade/arcade/cli/serve.py +++ b/arcade/arcade/cli/serve.py @@ -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)