auth and token middleware context update

This commit is contained in:
berkay 2025-01-26 20:18:06 +03:00
parent 3d5a43220e
commit a7e48d8755
17 changed files with 265 additions and 345 deletions

View File

@ -17,6 +17,12 @@ class WagRedis:
) )
class RedisAuthKeys:
AUTH: str = "AUTH"
OCCUPANT: str = "OCCUPANT"
EMPLOYEE: str = "EMPLOYEE"
class RedisCategoryKeys: class RedisCategoryKeys:
REBUILD: str = "REBUILD" REBUILD: str = "REBUILD"
ENDPOINT2CLASS: str = "ENDPOINT2CLASS" ENDPOINT2CLASS: str = "ENDPOINT2CLASS"
@ -28,6 +34,3 @@ class RedisCategoryKeys:
MENU_FIRST_LAYER: str = "MENU_FIRST_LAYER" MENU_FIRST_LAYER: str = "MENU_FIRST_LAYER"
PAGE_MAPPER: str = "PAGE_MAPPER" PAGE_MAPPER: str = "PAGE_MAPPER"
MENU_MAPPER: str = "MENU_MAPPER" MENU_MAPPER: str = "MENU_MAPPER"
AUTH: str = "AUTH"
OCCUPANT: str = "OCCUPANT"
EMPLOYEE: str = "EMPLOYEE"

View File

@ -16,23 +16,17 @@ class CreateEndpointFromCluster:
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.router: CategoryCluster = kwargs.get("router") self.router: CategoryCluster = kwargs.get("router")
self.method_endpoint: MethodToEvent = kwargs.get("method_endpoint") self.method_endpoint: MethodToEvent = kwargs.get("method_endpoint")
self.unique_id = str(uuid.uuid4())[:8] # Use first 8 chars of UUID for brevity
self.attach_router() self.attach_router()
def attach_router(self): def attach_router(self):
method = getattr(self.router, self.method_endpoint.METHOD.lower()) method = getattr(self.router, self.method_endpoint.METHOD.lower())
# Create a unique operation ID based on the endpoint path, method, and a unique identifier # Create a unique operation ID based on the endpoint path, method, and a unique identifier
base_path = self.method_endpoint.URL.strip('/').replace('/', '_').replace('-', '_')
operation_id = f"{base_path}_{self.method_endpoint.METHOD.lower()}_{self.unique_id}"
kwargs = { kwargs = {
"path": self.method_endpoint.URL, "path": self.method_endpoint.URL,
"summary": self.method_endpoint.SUMMARY, "summary": self.method_endpoint.SUMMARY,
"description": self.method_endpoint.DESCRIPTION, "description": self.method_endpoint.DESCRIPTION,
"operation_id": operation_id
} }
if hasattr(self.method_endpoint, 'RESPONSE_MODEL') and self.method_endpoint.RESPONSE_MODEL is not None: if hasattr(self.method_endpoint, 'RESPONSE_MODEL') and self.method_endpoint.RESPONSE_MODEL is not None:
kwargs["response_model"] = self.method_endpoint.RESPONSE_MODEL kwargs["response_model"] = self.method_endpoint.RESPONSE_MODEL

View File

@ -1,6 +1,6 @@
"""Token service for handling authentication tokens and user sessions.""" """Token service for handling authentication tokens and user sessions."""
from typing import List, Union, TypeVar, Dict, Any, Optional, TYPE_CHECKING from typing import List, Union, TypeVar, Dict, Any, TYPE_CHECKING
from ApiLayers.AllConfigs.Token.config import Auth from ApiLayers.AllConfigs.Token.config import Auth
from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error

View File

@ -0,0 +1,18 @@
from typing import Optional, Any
from pydantic import BaseModel
class DefaultContext(BaseModel):
...
class EventContext(DefaultContext):
auth: Any
code: str
url: str
class AuthContext(DefaultContext):
auth: Any
url: str

View File

@ -15,6 +15,7 @@ from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import BaseHTTPMiddleware
from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error 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.ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi
from ApiLayers.AllConfigs.Token.config import Auth from ApiLayers.AllConfigs.Token.config import Auth
from ApiLayers.ApiServices.Token.token_handler import TokenService from ApiLayers.ApiServices.Token.token_handler import TokenService
@ -87,14 +88,15 @@ class MiddlewareModule:
@wraps(func) @wraps(func)
async def wrapper(request: Request, *args, **kwargs): async def wrapper(request: Request, *args, **kwargs):
# Get and validate token context from request # Get and validate token context from request
auth_context = { endpoint_url = str(request.url.path)
"is_employee": False, auth_context = AuthContext(
"is_occupant": False, auth={"token": {"access_token": "", "refresher_token": "", "context": {}}},
"context": {} url=endpoint_url,
} )
# Set auth context on the wrapper function itself # Set auth context on the wrapper function itself
setattr(wrapper, 'auth', auth_context) setattr(func, 'auth_context', auth_context)
setattr(wrapper, 'auth_context', auth_context)
# Call the original endpoint function # Call the original endpoint function
if inspect.iscoroutinefunction(func): if inspect.iscoroutinefunction(func):
@ -102,6 +104,10 @@ class MiddlewareModule:
else: else:
result = func(request, *args, **kwargs) 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 result
return wrapper return wrapper

View File

@ -5,110 +5,22 @@ Token event middleware for handling authentication and event tracking.
import inspect import inspect
from functools import wraps from functools import wraps
from typing import Callable, Dict, Any, Optional, Union from typing import Callable, Dict, Any, Optional, Tuple, Union
from fastapi import Request from fastapi import Request
from pydantic import BaseModel from pydantic import BaseModel
from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error
from ApiLayers.ApiServices.Token.token_handler import TokenService 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.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi
from ApiLayers.Schemas import Events, EndpointRestriction 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 from .auth_middleware import MiddlewareModule
class EventFunctions:
def __init__(self, endpoint: str, request: Request):
self.endpoint = endpoint
self.request = request
def retrieve_function_dict(self) -> Optional[Dict[str, Any]]:
"""
Retrieve function dictionary for a given endpoint.
Args:
endpoint: The endpoint to retrieve the function dictionary for
Returns:
Dictionary containing the function dictionary
None if endpoint is not found
"""
access_token = TokenService.get_access_token_from_request(self.request)
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",
)
if not access_token:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Token not found",
)
db = EndpointRestriction.new_session()
restriction = EndpointRestriction.filter_one(
EndpointRestriction.endpoint_name == self.endpoint,
db=db,
).data
if not restriction:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Function code not found",
)
event_related = Events.filter_all(
Events.endpoint_id == restriction.id,
db=db,
).data
if not event_related:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="No event is registered for this user.",
)
an_event = event_related[0]
event_related_codes: list[str] = [
event.function_code for event in event_related
]
intersected_code: set = set(reachable_event_codes).intersection(
set(event_related_codes)
)
if not len(list(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": self.endpoint,
"reachable_event_code": list(intersected_code)[0],
"class": an_event.function_class,
}
class TokenEventMiddleware: class TokenEventMiddleware:
""" """
Module containing token and event handling functionality. Module containing token and event handling functionality.
@ -119,37 +31,18 @@ class TokenEventMiddleware:
""" """
@staticmethod @staticmethod
def match_endpoint_with_accessible_event(request_from_scope, endpoint_from_scope) -> Optional[Dict[str, Any]]: def retrieve_access_content(request_from_scope: Request) -> Tuple[str, list[str]]:
""" """
Match an endpoint with accessible events. Retrieves the access token and validates it.
Args: Args:
request_from_scope: The endpoint to match request_from_scope: The FastAPI request object
Returns: Returns:
Dict containing the endpoint registration data Tuple[str, list[str]]: The access token and a list of reachable event codes
None if endpoint is not found in database
""" """
# Get token context from request
access_token = TokenService.get_access_token_from_request(request_from_scope) access_token = TokenService.get_access_token_from_request(request_from_scope)
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",
)
if not access_token: if not access_token:
raise HTTPExceptionApi( raise HTTPExceptionApi(
error_code="", error_code="",
@ -158,12 +51,42 @@ class TokenEventMiddleware:
sys_msg="Token not found", sys_msg="Token not found",
) )
db = EndpointRestriction.new_session() # Get token context from Redis by access token and collect reachable event codes
restriction = EndpointRestriction.filter_one( token_context = TokenService.get_object_via_access_key(access_token=access_token)
EndpointRestriction.endpoint_name == endpoint_from_scope, if token_context.is_employee:
db=db, reachable_event_codes: list[str] = token_context.selected_company.reachable_event_codes
).data elif token_context.is_occupant:
if not restriction: 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]) -> str:
"""
Match an endpoint with accessible events.
Args:
request: The endpoint to match
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( raise HTTPExceptionApi(
error_code="", error_code="",
lang="en", lang="en",
@ -171,36 +94,16 @@ class TokenEventMiddleware:
sys_msg="Function code not found", sys_msg="Function code not found",
) )
event_related = Events.filter_all( # Intersect function codes with user accers objects available event codes
Events.endpoint_id == restriction.id, intersected_code = list(set(function_code_list_of_event) & set(reachable_event_codes))
db=db, if not len(intersected_code) == 1:
).data
if not event_related:
raise HTTPExceptionApi( raise HTTPExceptionApi(
error_code="", error_code="",
lang="en", lang="en",
loc=get_line_number_for_error(), loc=get_line_number_for_error(),
sys_msg="No event is registered for this user.", sys_msg="No event is registered for this user.",
) )
an_event = event_related[0] return endpoint_url, intersected_code[0]
event_related_codes: list[str] = [
event.function_code for event in event_related
]
intersected_code: set = set(reachable_event_codes).intersection(
set(event_related_codes)
)
if not len(list(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": endpoint_from_scope,
"reachable_event_code": list(intersected_code)[0],
"class": an_event.function_class,
}
@classmethod @classmethod
def event_required(cls, func: Callable) -> Callable: def event_required(cls, func: Callable) -> Callable:
@ -216,88 +119,22 @@ class TokenEventMiddleware:
Returns: Returns:
Callable: The wrapped function with both auth and event handling Callable: The wrapped function with both auth and event handling
""" """
# First apply authentication
authenticated_func = MiddlewareModule.auth_required(func)
@wraps(authenticated_func) @wraps(func)
async def wrapper(request: Request, *args, **kwargs) -> Dict[str, Any]: async def wrapper(request: Request, *args, **kwargs) -> Dict[str, Any]:
# Get the endpoint URL for matching with events
endpoint_url = str(request.url.path)
# Set func_code first # Get and validate token context from request
func_code = "8aytr-" # token_context, reachable_event_codes = cls.retrieve_access_content(request)
setattr(wrapper, 'func_code', func_code) token_context, reachable_event_codes = {"token": "context", "context": {}}, ["g1j8i6j7-9k4h-0h6l-4i3j-2j0k1k0j0i0k"]
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)
# Get auth context from the authenticated function's wrapper # Get auth context from the authenticated function's wrapper
auth_context = getattr(authenticated_func, 'auth', None) if token_context is not None:
print('auth_context', auth_context) setattr(wrapper, 'event_context', event_context)
if auth_context is not None: setattr(func, 'event_context', event_context)
setattr(wrapper, 'auth', auth_context)
# Execute the authenticated function and get its result # Execute the authenticated function and get its result
if inspect.iscoroutinefunction(authenticated_func):
result = await authenticated_func(request, *args, **kwargs)
else:
result = authenticated_func(request, *args, **kwargs)
return result
# Copy any existing attributes from the authenticated function
for attr in dir(authenticated_func):
if not attr.startswith('__'):
setattr(wrapper, attr, getattr(authenticated_func, attr))
return wrapper
@staticmethod
def validation_required(
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
"""
# First apply authentication
# authenticated_func = MiddlewareModule.auth_required(func)
authenticated_func = func
@wraps(authenticated_func)
async def wrapper(
request: Request, *args: Any, **kwargs: Any
) -> Union[Dict[str, Any], BaseModel]:
# Handle both async and sync functions
endpoint_asked = getattr(kwargs.get("data", None), "data", None).get(
"endpoint", None
)
if not endpoint_asked:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Endpoint not found",
)
func.func_code = cls.match_endpoint_with_accessible_event(
endpoint_url, request
)
if inspect.iscoroutinefunction(authenticated_func):
result = await authenticated_func(request, *args, **kwargs)
else:
result = authenticated_func(request, *args, **kwargs)
function_auth = getattr(authenticated_func, "auth", None)
wrapper.auth = function_auth
func.auth = function_auth
authenticated_func.auth = function_auth
if inspect.iscoroutinefunction(authenticated_func):
result = await authenticated_func(request, *args, **kwargs)
else:
result = authenticated_func(request, *args, **kwargs)
if inspect.iscoroutinefunction(func): if inspect.iscoroutinefunction(func):
result = await func(request, *args, **kwargs) result = await func(request, *args, **kwargs)
else: else:
@ -305,3 +142,41 @@ class TokenEventMiddleware:
return result return result
return wrapper 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

View File

@ -4,7 +4,10 @@ Handles dynamic route creation based on configurations.
""" """
from fastapi import Request from fastapi import Request
from Events.Engine.set_defaults.setClusters import PrepareRouting from Events.Engine.set_defaults.run import get_cluster_controller_group
from Events.Engine.set_defaults.setClusters import PrepareRouting, SetItems2Redis, PrepareEvents
routers = None
async def health_check(request: Request): async def health_check(request: Request):
@ -24,8 +27,11 @@ def get_all_routers() -> PrepareRouting:
Returns: Returns:
tuple: (routers, protected_routes) tuple: (routers, protected_routes)
""" """
from Events.Engine.set_defaults.run import get_cluster_controller_group global routers
if not routers:
cluster_list = get_cluster_controller_group() cluster_list = get_cluster_controller_group()
prepare_routing = PrepareRouting(cluster_controller_group=cluster_list) prepare_routing = PrepareRouting(cluster_controller_group=cluster_list)
return prepare_routing prepare_events = PrepareEvents(cluster_controller_group=cluster_list)
set_items_2_redis = SetItems2Redis(prepare_events=prepare_events)
return prepare_routing
return routers

View File

@ -4,7 +4,10 @@ Handles dynamic route creation based on configurations.
""" """
from fastapi import Request from fastapi import Request
from Events.Engine.set_defaults.setClusters import PrepareRouting from Events.Engine.set_defaults.run import get_cluster_controller_group
from Events.Engine.set_defaults.setClusters import PrepareRouting, SetItems2Redis, PrepareEvents
routers = None
async def health_check(request: Request): async def health_check(request: Request):
@ -24,10 +27,11 @@ def get_all_routers() -> PrepareRouting:
Returns: Returns:
tuple: (routers, protected_routes) tuple: (routers, protected_routes)
""" """
from Events.Engine.set_defaults.run import get_cluster_controller_group global routers
if not routers:
cluster_list = get_cluster_controller_group() cluster_list = get_cluster_controller_group()
prepare_routing = PrepareRouting(cluster_controller_group=cluster_list) prepare_routing = PrepareRouting(cluster_controller_group=cluster_list)
return prepare_routing prepare_events = PrepareEvents(cluster_controller_group=cluster_list)
set_items_2_redis = SetItems2Redis(prepare_events=prepare_events)
return prepare_routing
return routers

View File

@ -4,6 +4,7 @@ Authentication related API endpoints.
from typing import Union, Any, Dict from typing import Union, Any, Dict
from ApiLayers.ApiValidations.Custom.wrapper_contexts import AuthContext, EventContext
from ApiLayers.Middleware import MiddlewareModule, TokenEventMiddleware from ApiLayers.Middleware import MiddlewareModule, TokenEventMiddleware
from ApiLayers.ApiValidations.Request import EmployeeSelection, OccupantSelection from ApiLayers.ApiValidations.Request import EmployeeSelection, OccupantSelection
@ -39,7 +40,7 @@ AuthenticationLoginEventMethods = MethodToEvent(
}, },
headers=[], headers=[],
errors=[], errors=[],
url="/authentication/login", url="/login",
method="POST", method="POST",
summary="Login via domain and access key : [email] | [phone]", summary="Login via domain and access key : [email] | [phone]",
description="Login to the system via domain, access key : [email] | [phone]", description="Login to the system via domain, access key : [email] | [phone]",
@ -49,9 +50,7 @@ def authentication_login_with_domain_and_creds(
request: Request, request: Request,
data: EndpointBaseRequestModel, data: EndpointBaseRequestModel,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
function = AuthenticationLoginEventMethods.retrieve_event( function = AuthenticationLoginEventMethods.retrieve_event(event_function_code=f"{authentication_login_super_user_event.key}")
event_function_code=f"{authentication_login_super_user_event.key}"
)
return function.endpoint_callable(request=request, data=data) return function.endpoint_callable(request=request, data=data)
@ -65,7 +64,7 @@ AuthenticationSelectEventMethods = MethodToEvent(
decorators_list=[MiddlewareModule.auth_required], decorators_list=[MiddlewareModule.auth_required],
headers=[], headers=[],
errors=[], errors=[],
url="/authentication/select", url="/select",
method="POST", method="POST",
summary="Select company or occupant type", summary="Select company or occupant type",
description="Select company or occupant type", description="Select company or occupant type",
@ -95,7 +94,7 @@ AuthenticationCheckTokenEventMethods = MethodToEvent(
headers=[], headers=[],
errors=[], errors=[],
decorators_list=[MiddlewareModule.auth_required], decorators_list=[MiddlewareModule.auth_required],
url="/authentication/check-token", url="/check-token",
method="POST", method="POST",
summary="Check if token is valid", summary="Check if token is valid",
description="Check if access token is valid for user", description="Check if access token is valid for user",
@ -118,7 +117,7 @@ AuthenticationRefreshEventMethods = MethodToEvent(
headers=[], headers=[],
errors=[], errors=[],
decorators_list=[MiddlewareModule.auth_required], decorators_list=[MiddlewareModule.auth_required],
url="/authentication/refresh", url="/refresh",
method="POST", method="POST",
summary="Refresh user info", summary="Refresh user info",
description="Refresh user info using access token", description="Refresh user info using access token",
@ -143,7 +142,7 @@ AuthenticationChangePasswordEventMethods = MethodToEvent(
headers=[], headers=[],
errors=[], errors=[],
decorators_list=[MiddlewareModule.auth_required], decorators_list=[MiddlewareModule.auth_required],
url="/authentication/change-password", url="/change-password",
method="POST", method="POST",
summary="Change password", summary="Change password",
description="Change password with access token", description="Change password with access token",
@ -166,7 +165,7 @@ AuthenticationCreatePasswordEventMethods = MethodToEvent(
}, },
headers=[], headers=[],
errors=[], errors=[],
url="/authentication/create-password", url="/create-password",
method="POST", method="POST",
summary="Create password", summary="Create password",
description="Create password with password reset token requested via email", description="Create password with password reset token requested via email",
@ -189,7 +188,7 @@ AuthenticationDisconnectUserEventMethods = MethodToEvent(
decorators_list=[MiddlewareModule.auth_required], decorators_list=[MiddlewareModule.auth_required],
headers=[], headers=[],
errors=[], errors=[],
url="/authentication/disconnect", url="/disconnect",
method="POST", method="POST",
summary="Disconnect all sessions", summary="Disconnect all sessions",
description="Disconnect all sessions of user in access token", description="Disconnect all sessions of user in access token",
@ -210,28 +209,25 @@ AuthenticationLogoutEventMethods = MethodToEvent(
}, },
headers=[], headers=[],
errors=[], errors=[],
url="/authentication/logout", decorators_list=[TokenEventMiddleware.event_required],
url="/logout",
method="POST", method="POST",
summary="Logout user", summary="Logout user",
description="Logout only single session of user which domain is provided", description="Logout only single session of user which domain is provided",
) )
@TokenEventMiddleware.event_required
def authentication_logout_user(request: Request, data: EndpointBaseRequestModel): def authentication_logout_user(request: Request, data: EndpointBaseRequestModel):
function = AuthenticationLogoutEventMethods.retrieve_event( event_context: EventContext = getattr(authentication_logout_user, "event_context", None)
event_function_code=f"{authentication_logout_user_event.key}" print('event_context', event_context)
) function = AuthenticationLogoutEventMethods.retrieve_event(event_function_code=f"{event_context.code}")
print('authentication_logout_user', dict( function.endpoint_callable.event_context = event_context
auth=getattr(authentication_logout_user, "auth", None),
func_code=getattr(authentication_logout_user, "func_code", None),
))
function.endpoint_callable.auth = getattr(authentication_logout_user, "auth", None)
function.endpoint_callable.func_code = getattr(authentication_logout_user, "func_code", None)
return function.endpoint_callable(request=request, data=data) return function.endpoint_callable(request=request, data=data)
AuthenticationLogoutEventMethods.endpoint_callable = authentication_logout_user AuthenticationLogoutEventMethods.endpoint_callable = authentication_logout_user
AuthenticationRefreshTokenEventMethods = MethodToEvent( AuthenticationRefreshTokenEventMethods = MethodToEvent(
name="AuthenticationRefreshTokenEventMethods", name="AuthenticationRefreshTokenEventMethods",
events={ events={
@ -239,19 +235,22 @@ AuthenticationRefreshTokenEventMethods = MethodToEvent(
}, },
headers=[], headers=[],
errors=[], errors=[],
decorators_list=[MiddlewareModule.auth_required, ], decorators_list=[MiddlewareModule.auth_required],
url="/authentication/refresh-token", url="/refresh-token",
method="POST", method="POST",
summary="Refresh token", summary="Refresh token",
description="Refresh access token with refresher token", description="Refresh access token with refresher token",
) )
def authentication_refresher_token(request: Request, data: EndpointBaseRequestModel): def authentication_refresher_token(request: Request, data: EndpointBaseRequestModel):
token_dict = authentication_refresher_token.auth auth_context: AuthContext = getattr(authentication_refresher_token, "auth_context", None)
function = AuthenticationRefreshTokenEventMethods.retrieve_event( function = AuthenticationRefreshTokenEventMethods.retrieve_event(
event_function_code=f"{authentication_refresher_token_event.key}" event_function_code=f"{authentication_refresher_token_event.key}"
) )
return function.endpoint_callable(data=data, request=request, token_dict=token_dict) function.endpoint_callable.auth_context = auth_context
return function.endpoint_callable(data=data, request=request)
AuthenticationRefreshTokenEventMethods.endpoint_callable = authentication_refresher_token AuthenticationRefreshTokenEventMethods.endpoint_callable = authentication_refresher_token
@ -263,7 +262,7 @@ AuthenticationForgotPasswordEventMethods = MethodToEvent(
}, },
headers=[], headers=[],
errors=[], errors=[],
url="/authentication/forgot-password", url="/forgot-password",
method="POST", method="POST",
summary="Request password reset", summary="Request password reset",
description="Send an email to user for a valid password reset token", description="Send an email to user for a valid password reset token",
@ -289,7 +288,7 @@ AuthenticationResetPasswordEventMethods = MethodToEvent(
headers=[], headers=[],
errors=[], errors=[],
decorators_list=[MiddlewareModule.auth_required], decorators_list=[MiddlewareModule.auth_required],
url="/authentication/reset-password", url="/reset-password",
method="POST", method="POST",
summary="Reset password", summary="Reset password",
description="Reset user password", description="Reset user password",
@ -314,7 +313,7 @@ AuthenticationDownloadAvatarEventMethods = MethodToEvent(
headers=[], headers=[],
errors=[], errors=[],
decorators_list=[], decorators_list=[],
url="/authentication/download-avatar", url="/download-avatar",
method="POST", method="POST",
summary="Download avatar", summary="Download avatar",
description="Download avatar icon and profile info of user", description="Download avatar icon and profile info of user",

View File

@ -23,20 +23,20 @@ AuthCluster = CategoryCluster(
prefix="/authentication", prefix="/authentication",
description="Authentication cluster", description="Authentication cluster",
pageinfo=authentication_page_info, pageinfo=authentication_page_info,
endpoints=[ endpoints={
AuthenticationLoginEventMethods, "AuthenticationLoginEventMethods": AuthenticationLoginEventMethods,
AuthenticationLogoutEventMethods, "AuthenticationLogoutEventMethods": AuthenticationLogoutEventMethods,
AuthenticationRefreshTokenEventMethods, "AuthenticationRefreshTokenEventMethods": AuthenticationRefreshTokenEventMethods,
AuthenticationForgotPasswordEventMethods, "AuthenticationForgotPasswordEventMethods": AuthenticationForgotPasswordEventMethods,
AuthenticationChangePasswordEventMethods, "AuthenticationChangePasswordEventMethods": AuthenticationChangePasswordEventMethods,
AuthenticationCheckTokenEventMethods, "AuthenticationCheckTokenEventMethods": AuthenticationCheckTokenEventMethods,
AuthenticationCreatePasswordEventMethods, "AuthenticationCreatePasswordEventMethods": AuthenticationCreatePasswordEventMethods,
AuthenticationDisconnectUserEventMethods, "AuthenticationDisconnectUserEventMethods": AuthenticationDisconnectUserEventMethods,
AuthenticationDownloadAvatarEventMethods, "AuthenticationDownloadAvatarEventMethods": AuthenticationDownloadAvatarEventMethods,
AuthenticationResetPasswordEventMethods, "AuthenticationResetPasswordEventMethods": AuthenticationResetPasswordEventMethods,
AuthenticationRefreshEventMethods, "AuthenticationRefreshEventMethods": AuthenticationRefreshEventMethods,
AuthenticationSelectEventMethods, "AuthenticationSelectEventMethods": AuthenticationSelectEventMethods,
], },
include_in_schema=True, include_in_schema=True,
sub_category=[], sub_category=[],
) )

View File

@ -5,6 +5,7 @@ from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error
from ApiLayers.ApiServices.Login.user_login_handler import UserLoginModule from ApiLayers.ApiServices.Login.user_login_handler import UserLoginModule
from ApiLayers.ApiServices.Token.token_handler import TokenService from ApiLayers.ApiServices.Token.token_handler import TokenService
from ApiLayers.ApiValidations.Custom.token_objects import CompanyToken, OccupantToken from ApiLayers.ApiValidations.Custom.token_objects import CompanyToken, OccupantToken
from ApiLayers.ApiValidations.Custom.wrapper_contexts import AuthContext, EventContext
from ApiLayers.ErrorHandlers import HTTPExceptionApi from ApiLayers.ErrorHandlers import HTTPExceptionApi
from ApiLayers.Schemas import ( from ApiLayers.Schemas import (
BuildLivingSpace, BuildLivingSpace,
@ -372,14 +373,12 @@ def authentication_logout_user(request: Request, data: Any):
# selected_user.remove_refresher_token(domain=data.domain) # selected_user.remove_refresher_token(domain=data.domain)
# return ResponseHandler.success("Session is logged out", data=token_user) # return ResponseHandler.success("Session is logged out", data=token_user)
# return ResponseHandler.not_found("Logout is not successfully completed") # return ResponseHandler.not_found("Logout is not successfully completed")
token_dict = authentication_logout_user.auth
print('token_dict', token_dict) event_context: EventContext = getattr(authentication_logout_user, "event_context", None)
func_code = authentication_logout_user.func_code return event_context.model_dump()
print('func_code', func_code)
return
def authentication_refresher_token(request: Request, token_dict: TokenDictType, data: Any): def authentication_refresher_token(request: Request, data: Any):
"""Refresh access token with refresher token""" """Refresh access token with refresher token"""
# token_refresher = UsersTokens.filter_by_one( # token_refresher = UsersTokens.filter_by_one(
# token=data.refresh_token, # token=data.refresh_token,
@ -402,7 +401,8 @@ def authentication_refresher_token(request: Request, token_dict: TokenDictType,
# } # }
# return ResponseHandler.success("User is logged in successfully via refresher token", data=response_data) # return ResponseHandler.success("User is logged in successfully via refresher token", data=response_data)
# return ResponseHandler.not_found("Invalid data") # return ResponseHandler.not_found("Invalid data")
return auth_context: AuthContext = getattr(authentication_refresher_token, "auth_context", None)
return auth_context.model_dump()
def authentication_forgot_password(request: Request, data: Any): def authentication_forgot_password(request: Request, data: Any):

View File

@ -4,3 +4,10 @@ import Events.AllEvents.validations as validations_events
events_list = (auths_events, events_events, validations_events) events_list = (auths_events, events_events, validations_events)
def retrieve_cluster_by_name(cluster_name: str):
for module in events_list:
if hasattr(module, cluster_name, None):
return getattr(module, cluster_name, None)

View File

@ -135,11 +135,11 @@ class MethodToEvent:
return found_event return found_event
raise ValueError(f"Event with function code {event_function_code} not found") raise ValueError(f"Event with function code {event_function_code} not found")
def retrieve_redis_value(self, cluster_name: str) -> Dict: def retrieve_redis_value(self, cluster: "CategoryCluster") -> Dict:
""" """
Key("METHOD_FUNCTION_CODES:{ClusterToMethod}:MethodEvent:Endpoint") : Value([FUNCTION_CODE, ...]) Key("METHOD_FUNCTION_CODES:{ClusterToMethod}:MethodEvent:Endpoint") : Value([FUNCTION_CODE, ...])
""" """
redis_key = f"{RedisCategoryKeys.METHOD_FUNCTION_CODES}:{cluster_name}:{self.name}:{self.URL}" redis_key = f"{RedisCategoryKeys.METHOD_FUNCTION_CODES}:{cluster.name}:{self.name}:{f"{cluster.PREFIX}{self.URL}"}"
return {redis_key: self.retrieve_all_event_keys()} return {redis_key: self.retrieve_all_event_keys()}
@staticmethod @staticmethod
@ -156,8 +156,8 @@ class CategoryCluster:
PREFIX: str PREFIX: str
PAGEINFO: PageInfo PAGEINFO: PageInfo
DESCRIPTION: str DESCRIPTION: str
ENDPOINTS: list[MethodToEvent] # [MethodToEvent, ...] ENDPOINTS: dict[str, MethodToEvent] # {"MethodToEvent": MethodToEvent, ...}
SUBCATEGORY: Optional[List["CategoryCluster"]] # [CategoryCluster, ...] SUBCATEGORY: Optional[List["CategoryCluster"]] # [CategoryCluster, ...]
INCLUDE_IN_SCHEMA: Optional[bool] = True INCLUDE_IN_SCHEMA: Optional[bool] = True
def __init__( def __init__(
@ -167,7 +167,7 @@ class CategoryCluster:
prefix: str, prefix: str,
description: str, description: str,
pageinfo: PageInfo, pageinfo: PageInfo,
endpoints: list[MethodToEvent], endpoints: dict[str, MethodToEvent],
sub_category: list, sub_category: list,
include_in_schema: Optional[bool] = True, include_in_schema: Optional[bool] = True,
): ):
@ -176,7 +176,7 @@ class CategoryCluster:
self.PREFIX = prefix self.PREFIX = prefix
self.PAGEINFO = pageinfo self.PAGEINFO = pageinfo
self.DESCRIPTION = description self.DESCRIPTION = description
self.ENDPOINTS = endpoints or [] self.ENDPOINTS = endpoints or {}
self.SUBCATEGORY = sub_category or [] self.SUBCATEGORY = sub_category or []
self.INCLUDE_IN_SCHEMA = include_in_schema self.INCLUDE_IN_SCHEMA = include_in_schema
@ -189,7 +189,7 @@ class CategoryCluster:
RedisCategoryKeys.CLUSTER_2_METHOD_EVENT RedisCategoryKeys.CLUSTER_2_METHOD_EVENT
Returns the class name and function codes for the class. Returns the class name and function codes for the class.
""" """
dict_cluster_2_method, list_endpoints = {}, [i.name for i in self.ENDPOINTS] dict_cluster_2_method, list_endpoints = {}, [i.name for i in self.ENDPOINTS.values()]
for endpoint_name in list_endpoints: for endpoint_name in list_endpoints:
dict_cluster_2_method[endpoint_name] = self.name dict_cluster_2_method[endpoint_name] = self.name
dict_cluster_2_method[self.name] = list_endpoints dict_cluster_2_method[self.name] = list_endpoints
@ -200,7 +200,7 @@ class CategoryCluster:
Retrieves all function codes by iterating over the events list. Retrieves all function codes by iterating over the events list.
""" """
all_function_codes = [] all_function_codes = []
for event_method in self.ENDPOINTS: for event_method in self.ENDPOINTS.values():
all_function_codes.extend([str(event_key) for event_key in event_method.EVENTS.keys()]) all_function_codes.extend([str(event_key) for event_key in event_method.EVENTS.keys()])
return all_function_codes return all_function_codes

View File

@ -1,4 +1,5 @@
from Events.AllEvents.events_file import events_list from Events.AllEvents.events_file import events_list
from .category_cluster_models import cluster_controller from .category_cluster_models import cluster_controller
@ -7,10 +8,12 @@ def get_cluster_controller_group():
cluster_controller.import_all_category_clusters(cluster) cluster_controller.import_all_category_clusters(cluster)
return cluster_controller return cluster_controller
""" """
prepare_routing = PrepareRouting(cluster_controller_group=cluster_controller) prepare_routing = PrepareRouting(cluster_controller_group=cluster_controller)
prepare_events = PrepareEvents(cluster_controller_group=cluster_controller) prepare_events = PrepareEvents(cluster_controller_group=cluster_controller)
set_items_2_redis = SetItems2Redis(prepare_events=prepare_events)
print(set_items_2_redis) print(set_items_2_redis)
print(prepare_routing) print(prepare_routing)
""" """

View File

@ -38,7 +38,7 @@ class PrepareRouting(DecoratorModule):
return self.__safe_endpoint_list return self.__safe_endpoint_list
def create_endpoints(self, cluster: CategoryCluster, created_router): def create_endpoints(self, cluster: CategoryCluster, created_router):
for method_endpoint in list(cluster.ENDPOINTS): for method_endpoint in list(cluster.ENDPOINTS.values()):
# Filter out the original function and apply decorators # Filter out the original function and apply decorators
applied_decorators_qualname = self.apply_decorators(method_endpoint) applied_decorators_qualname = self.apply_decorators(method_endpoint)
# Register the endpoint with FastAPI router # Register the endpoint with FastAPI router
@ -89,17 +89,17 @@ class PrepareEvents(DecoratorModule):
f"{self.valid_redis_items.CLUSTER_FUNCTION_CODES_KEY}:{cluster.name}" : tuple(cluster.retrieve_all_function_codes()) f"{self.valid_redis_items.CLUSTER_FUNCTION_CODES_KEY}:{cluster.name}" : tuple(cluster.retrieve_all_function_codes())
} }
for method_endpoint in list(cluster.ENDPOINTS): for method_endpoint in list(cluster.ENDPOINTS.values()):
# [SAVE]REDIS => ENDPOINT2CLASS = {MethodEvent: Endpoint("/.../.../..."), ...} # [SAVE]REDIS => ENDPOINT2CLASS = {MethodEvent: Endpoint("/.../.../..."), ...}
self.valid_redis_items.ENDPOINT2CLASS_VALUE.update( self.valid_redis_items.ENDPOINT2CLASS_VALUE.update(
{f"{cluster.name}:{method_endpoint.name}": method_endpoint.URL} {f"{cluster.name}:{method_endpoint.name}": f"{cluster.PREFIX}{method_endpoint.URL}"}
) )
self.valid_redis_items.ENDPOINT2CLASS_VALUE.update( self.valid_redis_items.ENDPOINT2CLASS_VALUE.update(
{method_endpoint.URL :f"{cluster.name}:{method_endpoint.name}"} {f"{cluster.PREFIX}{method_endpoint.URL}" :f"{cluster.name}:{method_endpoint.name}"}
) )
# [SAVE]REDIS => METHOD_FUNCTION_CODES:MethodEvent:Endpoint = [FUNCTION_CODE, ...] # [SAVE]REDIS => METHOD_FUNCTION_CODES:MethodEvent:Endpoint = [FUNCTION_CODE, ...]
self.valid_redis_items.METHOD_FUNCTION_CODES_VALUE.update( self.valid_redis_items.METHOD_FUNCTION_CODES_VALUE.update(
method_endpoint.retrieve_redis_value(cluster_name=cluster.name) method_endpoint.retrieve_redis_value(cluster=cluster)
) )
@ -112,28 +112,22 @@ class SetItems2Redis:
self.set_items() self.set_items()
def __str__(self): def __str__(self):
self.std_out = f"\nSetItems2Redis:\n\n{self.std_out}" return f"\nSetItems2Redis:\n\n{self.std_out}"
return self.std_out
def set_items(self): def set_items(self):
from ApiLayers.AllConfigs.Redis.configs import RedisCategoryKeys from ApiLayers.AllConfigs.Redis.configs import RedisCategoryKeys
dict_prep = self.prepare_events.valid_redis_items.as_dict dict_prep = self.prepare_events.valid_redis_items.as_dict
RedisActions.delete( for redis_values_to_delete, redis_key_type in RedisCategoryKeys.__annotations__.items():
list_keys=[ if isinstance(redis_key_type, str):
f"{RedisCategoryKeys.MENU_FIRST_LAYER}:*", continue
f"{RedisCategoryKeys.CLUSTER_INDEX}:*", RedisActions.delete(list_keys=[f"{redis_values_to_delete}*"])
f"{RedisCategoryKeys.CLUSTER_FUNCTION_CODES}:*",
f"{RedisCategoryKeys.METHOD_FUNCTION_CODES}:*"
f"{RedisCategoryKeys.ENDPOINT2CLASS}:*",
]
)
# Save MENU_FIRST_LAYER to Redis # Save MENU_FIRST_LAYER to Redis
redis_list = RedisList(redis_key=RedisCategoryKeys.MENU_FIRST_LAYER) redis_list = RedisList(redis_key=RedisCategoryKeys.MENU_FIRST_LAYER)
RedisActions.set_json( RedisActions.set_json(
list_keys=redis_list.to_list(), value=dict_prep.get(RedisCategoryKeys.MENU_FIRST_LAYER) list_keys=redis_list.to_list(), value=dict_prep.get(RedisCategoryKeys.MENU_FIRST_LAYER)
) )
self.std_out = f"{RedisCategoryKeys.MENU_FIRST_LAYER}: {dict_prep.get(RedisCategoryKeys.MENU_FIRST_LAYER)}\n" self.std_out += f"{RedisCategoryKeys.MENU_FIRST_LAYER}: {dict_prep.get(RedisCategoryKeys.MENU_FIRST_LAYER)}\n"
# Save CLUSTER_INDEX to Redis # Save CLUSTER_INDEX to Redis
redis_list = RedisList(redis_key=RedisCategoryKeys.CLUSTER_INDEX) redis_list = RedisList(redis_key=RedisCategoryKeys.CLUSTER_INDEX)
@ -167,5 +161,11 @@ class SetItems2Redis:
self.std_out += f"\n{RedisCategoryKeys.ENDPOINT2CLASS}: {dict_prep.get(RedisCategoryKeys.ENDPOINT2CLASS)}\n" self.std_out += f"\n{RedisCategoryKeys.ENDPOINT2CLASS}: {dict_prep.get(RedisCategoryKeys.ENDPOINT2CLASS)}\n"
RedisActions.set_json( RedisActions.set_json(
list_keys=[f"{RedisCategoryKeys.REBUILD}:*"], value=False list_keys=[f"{RedisCategoryKeys.REBUILD}:*"], value={
f"{RedisCategoryKeys.MENU_FIRST_LAYER}": True,
f"{RedisCategoryKeys.CLUSTER_INDEX}": True,
f"{RedisCategoryKeys.CLUSTER_FUNCTION_CODES}": True,
f"{RedisCategoryKeys.METHOD_FUNCTION_CODES}": True,
f"{RedisCategoryKeys.ENDPOINT2CLASS}": True,
}
) )

View File

@ -58,9 +58,10 @@ class RedisResponse:
@property @property
def first(self) -> Union[RedisRow, None]: def first(self) -> Union[RedisRow, None]:
print("self.data", self.data)
if self.data: if self.data:
if isinstance(self.data, list): if isinstance(self.data, list):
if isinstance(self.data[0], RedisRow):
return self.data[0].row
return self.data[0] return self.data[0]
elif isinstance(self.data, RedisRow): elif isinstance(self.data, RedisRow):
return self.row return self.row

View File

@ -10,18 +10,22 @@ services:
dockerfile: DockerApiServices/AuthServiceApi/Dockerfile dockerfile: DockerApiServices/AuthServiceApi/Dockerfile
ports: ports:
- "41575:41575" - "41575:41575"
depends_on:
- init-service
event-service: # event-service:
build: # build:
context: . # context: .
dockerfile: DockerApiServices/EventServiceApi/Dockerfile # dockerfile: DockerApiServices/EventServiceApi/Dockerfile
ports: # ports:
- "41576:41576" # - "41576:41576"
# depends_on:
# - init-service
validation-service: # validation-service:
build: # build:
context: . # context: .
dockerfile: DockerApiServices/ValidationServiceApi/Dockerfile # dockerfile: DockerApiServices/ValidationServiceApi/Dockerfile
ports: # ports:
- "41577:41577" # - "41577:41577"
# and lets try to implement potry again in the dockerfile now we now that it is about copy of files # and lets try to implement potry again in the dockerfile now we now that it is about copy of files