Fixed bug in auth handler.

This commit is contained in:
arabcoders 2025-06-15 16:37:31 +03:00
parent 4367fb0937
commit 367d4a1aeb
2 changed files with 74 additions and 43 deletions

View file

@ -24,6 +24,8 @@ LOG: logging.Logger = logging.getLogger("http_api")
class HttpAPI: class HttpAPI:
di_context: dict[str, Any] = {}
def __init__(self, root_path: Path, queue: DownloadQueue): def __init__(self, root_path: Path, queue: DownloadQueue):
self.queue: DownloadQueue = queue or DownloadQueue.get_instance() self.queue: DownloadQueue = queue or DownloadQueue.get_instance()
self.encoder: Encoder = Encoder() self.encoder: Encoder = Encoder()
@ -33,6 +35,16 @@ class HttpAPI:
self.rootPath: Path = root_path self.rootPath: Path = root_path
self.cache = Cache() self.cache = Cache()
self.app: web.Application | None = None 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): async def on_shutdown(self, _: web.Application):
pass pass
@ -55,7 +67,6 @@ class HttpAPI:
app=app, app=app,
base_path=self.config.base_path.rstrip("/"), base_path=self.config.base_path.rstrip("/"),
download_path=self.config.download_path, download_path=self.config.download_path,
this=self,
) )
) )
@ -103,6 +114,12 @@ class HttpAPI:
async def options_handler(_: Request) -> Response: async def options_handler(_: Request) -> Response:
return web.Response(status=204) 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(): for route in get_routes(RouteType.HTTP).values():
routePath: str = f"/{route.path.lstrip('/')}" routePath: str = f"/{route.path.lstrip('/')}"
@ -113,7 +130,7 @@ class HttpAPI:
LOG.debug(f"Add ({route.name}) {route.method}: {route.path}.") 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: if route.path in registered_options:
continue continue
@ -131,6 +148,7 @@ class HttpAPI:
Args: Args:
username (str): The username. username (str): The username.
password (str): The password. password (str): The password.
this (HttpAPI): The instance of the HttpAPI.
Returns: Returns:
Awaitable: The middleware handler. Awaitable: The middleware handler.
@ -138,9 +156,9 @@ class HttpAPI:
""" """
@web.middleware @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 OPTIONS request, skip auth
if request.method == "OPTIONS": if "OPTIONS" == request.method:
return await handler(request) return await handler(request)
auth_header = request.headers.get("Authorization") auth_header = request.headers.get("Authorization")
@ -183,10 +201,15 @@ class HttpAPI:
if not (user_match and pass_match): if not (user_match and pass_match):
return web.json_response( 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) response = await handler(request)
if request.path != "/": if request.path != "/":
return response return response
@ -207,10 +230,10 @@ class HttpAPI:
return response return response
return middleware_handler return auth_handler
@staticmethod @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 @web.middleware
async def middleware_handler(request: Request, handler: RequestHandler) -> Response: async def middleware_handler(request: Request, handler: RequestHandler) -> Response:
static_path = str(app.router["download_static"].url_for(filename="")) static_path = str(app.router["download_static"].url_for(filename=""))
@ -231,41 +254,7 @@ class HttpAPI:
}, },
) )
kwargs: dict[str, Any] = { response = await handler(request)
"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,
)
contentType: str | None = response.headers.get("content-type", None) contentType: str | None = response.headers.get("content-type", None)
if contentType and "/" != base_path and contentType.startswith("text/html"): if contentType and "/" != base_path and contentType.startswith("text/html"):
@ -295,3 +284,44 @@ class HttpAPI:
return response return response
return middleware_handler 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

View file

@ -2,6 +2,7 @@ import functools
import inspect import inspect
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Any
import socketio import socketio
from aiohttp import web from aiohttp import web
@ -26,7 +27,7 @@ class HttpSocket:
config: Config config: Config
sio: socketio.AsyncServer sio: socketio.AsyncServer
queue: DownloadQueue queue: DownloadQueue
di_context: dict[str, object] = {} di_context: dict[str, Any] = {}
def __init__( def __init__(
self, self,