latest version of apis event and cahce ablitites added
This commit is contained in:
14
ApiLayers/Middleware/__init__.py
Normal file
14
ApiLayers/Middleware/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from .token_event_middleware import TokenEventMiddleware
|
||||
from .auth_middleware import (
|
||||
LoggerTimingMiddleware,
|
||||
RequestTimingMiddleware,
|
||||
MiddlewareModule,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"TokenEventMiddleware",
|
||||
"RequestTimingMiddleware",
|
||||
"MiddlewareModule",
|
||||
"LoggerTimingMiddleware",
|
||||
]
|
||||
175
ApiLayers/Middleware/auth_middleware.py
Normal file
175
ApiLayers/Middleware/auth_middleware.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
Authentication and Authorization middleware for FastAPI applications.
|
||||
|
||||
This module provides authentication decorator for protecting endpoints
|
||||
and a middleware for request timing measurements.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
|
||||
from time import perf_counter
|
||||
from typing import Callable
|
||||
from functools import wraps
|
||||
|
||||
from fastapi import Request, Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error
|
||||
from ApiLayers.ApiValidations.Custom.wrapper_contexts import AuthContext
|
||||
from ApiLayers.ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi
|
||||
from ApiLayers.AllConfigs.Token.config import Auth
|
||||
from ApiLayers.ApiServices.Token.token_handler import TokenService
|
||||
|
||||
|
||||
class MiddlewareModule:
|
||||
"""
|
||||
Middleware module for handling authentication and request timing.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_user_from_request(request: Request) -> dict:
|
||||
"""
|
||||
Get authenticated token context from request.
|
||||
|
||||
Args:
|
||||
request: FastAPI request object
|
||||
|
||||
Returns:
|
||||
AuthContext: Context containing the authenticated token data
|
||||
|
||||
Raises:
|
||||
HTTPExceptionApi: If token is missing, invalid, or user not found
|
||||
"""
|
||||
|
||||
# Get token and validate - will raise HTTPExceptionApi if invalid
|
||||
redis_token = TokenService.get_access_token_from_request(request=request)
|
||||
# Get token context - will validate token and raise appropriate errors
|
||||
token_context = TokenService.get_object_via_access_key(access_token=redis_token)
|
||||
if not token_context:
|
||||
raise HTTPExceptionApi(
|
||||
error_code="USER_NOT_FOUND",
|
||||
lang="tr",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="TokenService: Token Context couldnt retrieved from redis",
|
||||
)
|
||||
return token_context
|
||||
|
||||
@classmethod
|
||||
def auth_required(cls, func: Callable) -> Callable:
|
||||
"""
|
||||
Decorator for protecting FastAPI endpoints with authentication.
|
||||
|
||||
Usage:
|
||||
@router.get("/protected")
|
||||
@MiddlewareModule.auth_required
|
||||
async def protected_endpoint(request: Request):
|
||||
auth = protected_endpoint.auth # Access auth context
|
||||
if auth.is_employee:
|
||||
# Handle employee logic
|
||||
employee_id = auth.token_context.employee_id
|
||||
else:
|
||||
# Handle occupant logic
|
||||
occupant_id = auth.token_context.occupant_id
|
||||
return {"user_id": auth.user_id}
|
||||
|
||||
Args:
|
||||
func: The FastAPI route handler function to protect
|
||||
|
||||
Returns:
|
||||
Callable: Wrapped function that checks authentication before execution
|
||||
|
||||
Raises:
|
||||
HTTPExceptionApi: If authentication fails
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(request: Request, *args, **kwargs):
|
||||
# Get and validate token context from request
|
||||
endpoint_url = str(request.url.path)
|
||||
token_context = cls.get_user_from_request(request=request)
|
||||
auth_context = AuthContext(
|
||||
auth=token_context, url=endpoint_url, request=request
|
||||
)
|
||||
|
||||
# Set auth context on the wrapper function itself
|
||||
setattr(func, "auth_context", auth_context)
|
||||
setattr(wrapper, "auth_context", auth_context)
|
||||
|
||||
# Call the original endpoint function
|
||||
if inspect.iscoroutinefunction(func):
|
||||
result = await func(request, *args, **kwargs)
|
||||
else:
|
||||
result = func(request, *args, **kwargs)
|
||||
|
||||
# Set auth context on the wrapper function itself
|
||||
setattr(func, "auth_context", auth_context)
|
||||
setattr(wrapper, "auth_context", auth_context)
|
||||
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class RequestTimingMiddleware(BaseHTTPMiddleware):
|
||||
"""
|
||||
Middleware for measuring and logging request timing.
|
||||
Only handles timing, no authentication.
|
||||
"""
|
||||
|
||||
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
||||
"""
|
||||
Process each request through the middleware.
|
||||
|
||||
Args:
|
||||
request: FastAPI request object
|
||||
call_next: Next middleware in the chain
|
||||
|
||||
Returns:
|
||||
Response: Processed response with timing headers
|
||||
"""
|
||||
start_time = perf_counter()
|
||||
# Process the request
|
||||
response = await call_next(request)
|
||||
|
||||
# Add timing information to response headers
|
||||
end_time = perf_counter()
|
||||
elapsed = (end_time - start_time) * 1000 # Convert to milliseconds
|
||||
|
||||
response.headers.update(
|
||||
{
|
||||
"request-start": f"{start_time:.6f}",
|
||||
"request-end": f"{end_time:.6f}",
|
||||
"request-duration": f"{elapsed:.2f}ms",
|
||||
}
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class LoggerTimingMiddleware(BaseHTTPMiddleware):
|
||||
"""
|
||||
Middleware for measuring and logging request timing.
|
||||
Only handles timing, no authentication.
|
||||
"""
|
||||
|
||||
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
||||
# Log the request
|
||||
import arrow
|
||||
|
||||
headers = dict(request.headers)
|
||||
response = await call_next(request)
|
||||
# Log the response
|
||||
print(
|
||||
"Loggers :",
|
||||
{
|
||||
"url": request.url,
|
||||
"method": request.method,
|
||||
"access_token": headers.get(Auth.ACCESS_TOKEN_TAG, ""),
|
||||
"referer": headers.get("referer", ""),
|
||||
"origin": headers.get("origin", ""),
|
||||
"user-agent": headers.get("user-agent", ""),
|
||||
"datetime": arrow.now().format("YYYY-MM-DD HH:mm:ss ZZ"),
|
||||
"status_code": response.status_code,
|
||||
},
|
||||
)
|
||||
return response
|
||||
0
ApiLayers/Middleware/function_wrappers.py
Normal file
0
ApiLayers/Middleware/function_wrappers.py
Normal file
198
ApiLayers/Middleware/token_event_middleware.py
Normal file
198
ApiLayers/Middleware/token_event_middleware.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""
|
||||
Token event middleware for handling authentication and event tracking.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
|
||||
from functools import wraps
|
||||
from typing import Callable, Dict, Any, Optional, Tuple, Union
|
||||
from fastapi import Request
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error
|
||||
from ApiLayers.ApiServices.Token.token_handler import TokenService
|
||||
from ApiLayers.ApiValidations.Custom.wrapper_contexts import EventContext
|
||||
from ApiLayers.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi
|
||||
from ApiLayers.Schemas import Events, EndpointRestriction
|
||||
from ApiLayers.AllConfigs.Redis.configs import RedisCategoryKeys
|
||||
|
||||
from Services.Redis.Actions.actions import RedisActions
|
||||
|
||||
from .auth_middleware import MiddlewareModule
|
||||
|
||||
|
||||
class TokenEventMiddleware:
|
||||
"""
|
||||
Module containing token and event handling functionality.
|
||||
|
||||
This class provides:
|
||||
- Token and event context management
|
||||
- Event validation decorator for endpoints
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def retrieve_access_content(request_from_scope: Request) -> Tuple[str, list[str]]:
|
||||
"""
|
||||
Retrieves the access token and validates it.
|
||||
|
||||
Args:
|
||||
request_from_scope: The FastAPI request object
|
||||
|
||||
Returns:
|
||||
Tuple[str, list[str]]: The access token and a list of reachable event codes
|
||||
"""
|
||||
# Get token context from request
|
||||
access_token = TokenService.get_access_token_from_request(request_from_scope)
|
||||
if not access_token:
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Token not found",
|
||||
)
|
||||
|
||||
# Get token context from Redis by access token and collect reachable event codes
|
||||
token_context = TokenService.get_object_via_access_key(
|
||||
access_token=access_token
|
||||
)
|
||||
if token_context.is_employee:
|
||||
reachable_event_codes: list[str] = (
|
||||
token_context.selected_company.reachable_event_codes
|
||||
)
|
||||
elif token_context.is_occupant:
|
||||
reachable_event_codes: list[str] = (
|
||||
token_context.selected_occupant.reachable_event_codes
|
||||
)
|
||||
else:
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Token not found",
|
||||
)
|
||||
return token_context, reachable_event_codes
|
||||
|
||||
@staticmethod
|
||||
def retrieve_intersected_event_code(
|
||||
request: Request, reachable_event_codes: list[str]
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
Match an endpoint with accessible events.
|
||||
|
||||
Args:
|
||||
request: The endpoint to match
|
||||
reachable_event_codes: The list of event codes accessible to the user
|
||||
|
||||
Returns:
|
||||
Dict containing the endpoint registration data
|
||||
None if endpoint is not found in database
|
||||
"""
|
||||
endpoint_url = str(request.url.path)
|
||||
# Get the endpoint URL for matching with events
|
||||
function_codes_of_endpoint = RedisActions.get_json(
|
||||
list_keys=[RedisCategoryKeys.METHOD_FUNCTION_CODES, "*", endpoint_url]
|
||||
)
|
||||
function_code_list_of_event = function_codes_of_endpoint.first
|
||||
if not function_codes_of_endpoint.status:
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Function code not found",
|
||||
)
|
||||
|
||||
# Intersect function codes with user accers objects available event codes
|
||||
# reachable_event_codes = ["36a165fe-a2f3-437b-80ee-1ee44670fe70"]
|
||||
intersected_code = list(
|
||||
set(function_code_list_of_event) & set(reachable_event_codes)
|
||||
)
|
||||
if not len(intersected_code) == 1:
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="No event is registered for this user.",
|
||||
)
|
||||
return endpoint_url, intersected_code[0]
|
||||
|
||||
@classmethod
|
||||
def event_required(cls, func: Callable) -> Callable:
|
||||
"""
|
||||
Decorator for endpoints with token and event requirements.
|
||||
This decorator:
|
||||
1. First validates authentication using MiddlewareModule.auth_required
|
||||
2. Then adds event tracking context
|
||||
|
||||
Args:
|
||||
func: The function to be decorated
|
||||
|
||||
Returns:
|
||||
Callable: The wrapped function with both auth and event handling
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(request: Request, *args, **kwargs) -> Dict[str, Any]:
|
||||
|
||||
# Get and validate token context from request
|
||||
token_context, reachable_event_codes = cls.retrieve_access_content(request)
|
||||
endpoint_url, reachable_event_code = cls.retrieve_intersected_event_code(
|
||||
request, reachable_event_codes
|
||||
)
|
||||
event_context = EventContext(
|
||||
auth=token_context,
|
||||
code=reachable_event_code,
|
||||
url=endpoint_url,
|
||||
request=request,
|
||||
)
|
||||
|
||||
# Get auth context from the authenticated function's wrapper
|
||||
if token_context is not None:
|
||||
setattr(wrapper, "event_context", event_context)
|
||||
setattr(func, "event_context", event_context)
|
||||
|
||||
# Execute the authenticated function and get its result
|
||||
if inspect.iscoroutinefunction(func):
|
||||
result = await func(request, *args, **kwargs)
|
||||
else:
|
||||
result = func(request, *args, **kwargs)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
# event_required is already sets function_code state to wrapper
|
||||
# @classmethod
|
||||
# def validation_required(cls, func: Callable[..., Dict[str, Any]]) -> Callable[..., Dict[str, Any]]:
|
||||
# """
|
||||
# Decorator for endpoints with token and event requirements.
|
||||
# This decorator:
|
||||
# 1. First validates authentication using MiddlewareModule.auth_required
|
||||
# 2. Then adds event tracking context
|
||||
|
||||
# Args:
|
||||
# func: The function to be decorated
|
||||
|
||||
# Returns:
|
||||
# Callable: The wrapped function with both auth and event handling
|
||||
# """
|
||||
|
||||
# @wraps(func)
|
||||
# async def wrapper(request: Request, *args: Any, **kwargs: Any) -> Union[Dict[str, Any], BaseModel]:
|
||||
|
||||
# # Get and validate token context from request
|
||||
# token_context, reachable_event_codes = cls.retrieve_access_content(request)
|
||||
# endpoint_url, reachable_event_code = cls.retrieve_intersected_event_code(request, reachable_event_codes)
|
||||
|
||||
# # Get auth context from the authenticated function's wrapper
|
||||
# if token_context is not None:
|
||||
# setattr(wrapper, 'auth', token_context)
|
||||
# setattr(wrapper, 'url', endpoint_url)
|
||||
# setattr(wrapper, 'func_code', reachable_event_code)
|
||||
|
||||
# # Execute the authenticated function and get its result
|
||||
# if inspect.iscoroutinefunction(func):
|
||||
# result = await func(request, *args, **kwargs)
|
||||
# else:
|
||||
# result = func(request, *args, **kwargs)
|
||||
# return result
|
||||
|
||||
# return wrapper
|
||||
Reference in New Issue
Block a user