Coverage for src/lexigram/auth/web/middleware/response_handler.py: 34%
29 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 12:26 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 12:26 +0800
1from __future__ import annotations
3from typing import Any
5from lexigram.auth import constants as const
8class AuthResponseHandler:
9 """Handles authentication-related HTTP responses."""
11 @staticmethod
12 async def unauthorized_response(
13 message: str = "Authentication required",
14 request: Any = None,
15 response_factory: Any | None = None,
16 ) -> Any:
17 """Return 401 Unauthorized response or redirect to login."""
18 rf = response_factory or await _get_response_factory(request)
20 if request and rf:
21 # Check if client accepts HTML
22 accept = request.headers.get("Accept", "")
23 if "text/html" in accept:
24 login_url = getattr(request, "login_url", "/login")
25 return rf.redirect(
26 f"{login_url}?next={request.url.path}",
27 status_code=302,
28 )
30 if rf:
31 return rf.json(
32 status_code=401,
33 content={"error": "unauthorized", "message": message},
34 headers={"WWW-Authenticate": f'{const.DEFAULT_TOKEN_TYPE} realm="api"'},
35 )
37 raise RuntimeError(
38 "ResponseFactoryProtocol not available for unauthorized_response — ensure DI container is configured",
39 )
41 @staticmethod
42 async def forbidden_response(
43 message: str = "Insufficient permissions",
44 request: Any | None = None,
45 response_factory: Any | None = None,
46 ) -> Any:
47 """Return 403 Forbidden response."""
48 rf = response_factory or await _get_response_factory(request)
49 if rf:
50 return rf.json(
51 status_code=403,
52 content={"error": "forbidden", "message": message},
53 )
55 raise RuntimeError(
56 "ResponseFactoryProtocol not available for forbidden_response — ensure DI container is configured",
57 )
60async def _get_response_factory(context: Any | None = None) -> Any:
61 """Resolve `ResponseFactoryProtocol` from context or global container."""
62 from lexigram.contracts.web import ResponseFactoryProtocol
63 from lexigram.di.resolution.context import get_resolver
65 resolver = get_resolver(context)
66 if not resolver:
67 return None
69 return await resolver.resolve_optional(ResponseFactoryProtocol)
72__all__ = [
73 "AuthResponseHandler",
74]