From 367d4a1aeb5cc61939460abade74cbdb4d0d56b9 Mon Sep 17 00:00:00 2001 From: arabcoders Date: Sun, 15 Jun 2025 16:37:31 +0300 Subject: [PATCH] Fixed bug in auth handler. --- app/library/HttpAPI.py | 114 ++++++++++++++++++++++++-------------- app/library/HttpSocket.py | 3 +- 2 files changed, 74 insertions(+), 43 deletions(-) diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py index 27f4a241..03185baa 100644 --- a/app/library/HttpAPI.py +++ b/app/library/HttpAPI.py @@ -24,6 +24,8 @@ LOG: logging.Logger = logging.getLogger("http_api") class HttpAPI: + di_context: dict[str, Any] = {} + def __init__(self, root_path: Path, queue: DownloadQueue): self.queue: DownloadQueue = queue or DownloadQueue.get_instance() self.encoder: Encoder = Encoder() @@ -33,6 +35,16 @@ class HttpAPI: self.rootPath: Path = root_path self.cache = Cache() self.app: web.Application | None = None + self.di_context: dict[str, Any] = { + "queue": self.queue, + "encoder": self.encoder, + "config": self.config, + "notify": self._notify, + "cache": self.cache, + "app": self.app, + "http_api": self, + "root_path": self.rootPath, + } async def on_shutdown(self, _: web.Application): pass @@ -55,7 +67,6 @@ class HttpAPI: app=app, base_path=self.config.base_path.rstrip("/"), download_path=self.config.download_path, - this=self, ) ) @@ -103,6 +114,12 @@ class HttpAPI: async def options_handler(_: Request) -> Response: return web.Response(status=204) + def _handle(handler): + async def wrapped(request): + return await self._handle(handler, request) + + return wrapped + for route in get_routes(RouteType.HTTP).values(): routePath: str = f"/{route.path.lstrip('/')}" @@ -113,7 +130,7 @@ class HttpAPI: LOG.debug(f"Add ({route.name}) {route.method}: {route.path}.") - app.router.add_route(route.method, route.path, handler=route.handler, name=route.name) + app.router.add_route(route.method, route.path, handler=_handle(route.handler), name=route.name) if route.path in registered_options: continue @@ -131,6 +148,7 @@ class HttpAPI: Args: username (str): The username. password (str): The password. + this (HttpAPI): The instance of the HttpAPI. Returns: Awaitable: The middleware handler. @@ -138,9 +156,9 @@ class HttpAPI: """ @web.middleware - async def middleware_handler(request: Request, handler: RequestHandler) -> Response: + async def auth_handler(request: Request, handler: RequestHandler) -> Response: # if OPTIONS request, skip auth - if request.method == "OPTIONS": + if "OPTIONS" == request.method: return await handler(request) auth_header = request.headers.get("Authorization") @@ -183,10 +201,15 @@ class HttpAPI: if not (user_match and pass_match): return web.json_response( - data={"error": "Unauthorized (Invalid credentials)."}, status=web.HTTPUnauthorized.status_code + data={"error": "Unauthorized (Invalid credentials)."}, + status=web.HTTPUnauthorized.status_code, + headers={ + "WWW-Authenticate": 'Basic realm="Authorization Required."', + }, ) response = await handler(request) + if request.path != "/": return response @@ -207,10 +230,10 @@ class HttpAPI: return response - return middleware_handler + return auth_handler @staticmethod - def middle_wares(app: web.Application, base_path: str, download_path: str, this: "HttpAPI") -> Awaitable: + def middle_wares(app: web.Application, base_path: str, download_path: str) -> Awaitable: @web.middleware async def middleware_handler(request: Request, handler: RequestHandler) -> Response: static_path = str(app.router["download_static"].url_for(filename="")) @@ -231,41 +254,7 @@ class HttpAPI: }, ) - kwargs: dict[str, Any] = { - "request": request, - "queue": this.queue, - "encoder": this.encoder, - "config": this.config, - "notify": this._notify, - "cache": this.cache, - "app": app, - "http_api": this, - "root_path": this.rootPath, - } - - try: - sig = inspect.signature(handler) - expected_args = sig.parameters.keys() - - try: - if 1 == len(expected_args) and "request" in expected_args: - response = await handler(request) - else: - filtered = {k: v for k, v in kwargs.items() if k in expected_args} - response = await handler(**filtered) - except TypeError as te: - if "missing 1 required positional argument" in str(te) and "request" in str(te): - response = await handler(request) - else: - raise - except web.HTTPException as e: - return web.json_response(data={"error": str(e)}, status=e.status_code) - except Exception as e: - LOG.exception(e) - response = web.json_response( - data={"error": "Internal Server Error"}, - status=web.HTTPInternalServerError.status_code, - ) + response = await handler(request) contentType: str | None = response.headers.get("content-type", None) if contentType and "/" != base_path and contentType.startswith("text/html"): @@ -295,3 +284,44 @@ class HttpAPI: return response return middleware_handler + + async def _handle(self, handler: RequestHandler, request: Request) -> Response: + """ + Call the handler with the request and return the response. + + Args: + handler (RequestHandler): The handler to call. + request (Request): The request object. + + Returns: + Response: The response object. + + """ + context = {**self.di_context.copy(), "request": request} + + try: + sig = inspect.signature(handler) + expected_args = sig.parameters.keys() + + try: + if 1 == len(expected_args) and "request" in expected_args: + response = await handler(request) + else: + filtered = {k: v for k, v in context.items() if k in expected_args} + response = await handler(**filtered) + except TypeError as te: + LOG.exception(te) + if "missing 1 required positional argument" in str(te) and "request" in str(te): + response = await handler(request) + else: + raise + except web.HTTPException as e: + return web.json_response(data={"error": str(e)}, status=e.status_code) + except Exception as e: + LOG.exception(e) + response = web.json_response( + data={"error": "Internal Server Error"}, + status=web.HTTPInternalServerError.status_code, + ) + + return response diff --git a/app/library/HttpSocket.py b/app/library/HttpSocket.py index 6975bcd7..7c00e212 100644 --- a/app/library/HttpSocket.py +++ b/app/library/HttpSocket.py @@ -2,6 +2,7 @@ import functools import inspect import logging from pathlib import Path +from typing import Any import socketio from aiohttp import web @@ -26,7 +27,7 @@ class HttpSocket: config: Config sio: socketio.AsyncServer queue: DownloadQueue - di_context: dict[str, object] = {} + di_context: dict[str, Any] = {} def __init__( self,