From f7eedb5ea0d54d40a99583ab25586fca3991f24f Mon Sep 17 00:00:00 2001 From: berkay Date: Wed, 29 Jan 2025 15:58:42 +0300 Subject: [PATCH] auth updated routes tested & password is not yet tested --- .idea/workspace.xml | 26 ++++---- ApiLayers/ApiServices/Token/token_handler.py | 42 ++++++------ .../Response/authentication/auth.py | 28 +++++++- .../authentication/auth/api_events.py | 6 +- Events/AllEvents/authentication/auth/auth.py | 37 ++++++----- .../authentication/auth/function_handlers.py | 64 ++++++++++++------- Events/AllEvents/authentication/auth/info.py | 2 +- .../AllEvents/authentication/auth/models.py | 4 ++ Events/Engine/abstract_class.py | 5 +- Events/base_request_model.py | 15 ++++- Services/Redis/Models/access.py | 2 +- 11 files changed, 147 insertions(+), 84 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 92122e5..1738102 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -6,18 +6,16 @@ - - - - + - + + - - + + - { + "keyToString": { + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "git-widget-placeholder": "development", + "last_opened_file_path": "/home/berkay/git-gitea-evyos/wag-managment-api-service-version-5/ApiLayers/LanguageModels/templates" } -}]]> +} diff --git a/ApiLayers/ApiServices/Token/token_handler.py b/ApiLayers/ApiServices/Token/token_handler.py index e10bfc2..362ab0b 100644 --- a/ApiLayers/ApiServices/Token/token_handler.py +++ b/ApiLayers/ApiServices/Token/token_handler.py @@ -2,6 +2,8 @@ from typing import List, Union, TypeVar, Dict, Any, TYPE_CHECKING +import arrow + from ApiLayers.AllConfigs.Token.config import Auth from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error from ApiLayers.ApiLibrary.date_time_actions.date_functions import DateTimeLocal @@ -51,11 +53,7 @@ class TokenService: @classmethod def _get_user_tokens(cls, user: Users) -> RedisResponse: """Get all tokens for a user from Redis.""" - return RedisActions.get_json( - list_keys=AccessToken( - userUUID=user.uu_id, - ).to_list() - ) + return RedisActions.get_json(list_keys=[f"*:{str(user.uu_id)}"]) @classmethod def do_occupant_login( @@ -147,6 +145,7 @@ class TokenService: userUUID=user.uu_id, accessToken=cls._create_access_token(), ) + cls.remove_token_with_domain(user=user, domain=model.get("domain")) redis_action = RedisActions.set_json( list_keys=access_object.to_list(), value=model, @@ -282,8 +281,9 @@ class TokenService: remember: bool, ) -> Dict[str, Any]: """Set access token to redis and handle user session.""" + from ApiLayers.AllConfigs.Token.config import Auth cls.remove_token_with_domain(user=user, domain=domain) - Users.client_arrow = DateTimeLocal(is_client=True, timezone=user.local_timezone) + # Users.client_arrow = DateTimeLocal(is_client=True, timezone=user.local_timezone) login_dict, db_session = {}, UsersTokens.new_session() if user.is_occupant: # Handle login based on user type login_dict = cls.do_occupant_login( @@ -295,30 +295,35 @@ class TokenService: ) # Handle remember me functionality + user.remember_me = bool(remember) if remember: + users_token_created = cls._create_access_token(access=False) + login_dict["refresh_token"] = users_token_created users_token = UsersTokens.find_or_create( db=db_session, user_id=user.id, token_type="RememberMe", - token=cls._create_access_token(access=False), domain=domain, ) - if users_token.meta_data.get("created"): - user.remember_me = True + if users_token.meta_data.created: + users_token.token = users_token_created + users_token.save(db=db_session) + else: + if arrow.now() > arrow.get(str(users_token.expires_at)): # Check if token is expired + users_token.token = users_token_created + users_token.expires_at = str(arrow.now().datetime + Auth.TOKEN_EXPIRE_DAY_1) + users_token.save(db=db_session) + else: + login_dict["refresh_token"] = users_token.token else: - if UsersTokens.filter_all( + already_refresher = UsersTokens.filter_all( UsersTokens.user_id == user.id, UsersTokens.token_type == "RememberMe", UsersTokens.domain == domain, db=db_session, - ).data: - UsersTokens.filter_all( - UsersTokens.user_id == user.id, - UsersTokens.token_type == "RememberMe", - UsersTokens.domain == domain, - db=db_session, - ).query.delete(synchronize_session=False) - user.remember_me = False + ) + if already_refresher.count: + already_refresher.query.delete(synchronize_session=False) user.save(db=db_session) return {**login_dict, "user": user.get_dict()} @@ -399,7 +404,6 @@ class TokenService: return EmployeeTokenObject(**redis_object) elif redis_object.get("user_type") == UserType.occupant.value: return OccupantTokenObject(**redis_object) - raise HTTPExceptionApi( error_code="", lang="en", diff --git a/ApiLayers/LanguageModels/Response/authentication/auth.py b/ApiLayers/LanguageModels/Response/authentication/auth.py index 067ea6f..8edef2a 100644 --- a/ApiLayers/LanguageModels/Response/authentication/auth.py +++ b/ApiLayers/LanguageModels/Response/authentication/auth.py @@ -49,12 +49,20 @@ authResponses = { "message": "Password changed successfully.", }, }, - "DISCONNECTED_USER": { + "LOGOUT_USER": { "tr": { "message": "Kullanıcı başarılı bir şekilde çıkış yaptı.", }, "en": { - "message": "User logged out successfully.", + "message": "User successfully logged out.", + }, + }, + "DISCONNECTED_USER": { + "tr": { + "message": "Kullanıcı tüm cihazlardan başarılı bir şekilde çıkış yaptı.", + }, + "en": { + "message": "User successfully logged out of all devices.", }, }, "USER_NOT_FOUND": { @@ -72,5 +80,21 @@ authResponses = { "en": { "message": "Password reset request created successfully.", }, + }, + "USER_AVATAR": { + "tr": { + "message": "Kullanıcı avatarı data blogunda belirtildiği şekildedir.", + }, + "en": { + "message": "User avatar is as specified in the data block.", + }, + }, + "TOKEN_REFRESH": { + "tr": { + "message": "Token başarılı bir şekilde yenilendi.", + }, + "en": { + "message": "Token successfully refreshed.", + }, } } diff --git a/Events/AllEvents/authentication/auth/api_events.py b/Events/AllEvents/authentication/auth/api_events.py index 53e32ab..6e9d44d 100644 --- a/Events/AllEvents/authentication/auth/api_events.py +++ b/Events/AllEvents/authentication/auth/api_events.py @@ -124,7 +124,7 @@ authentication_disconnect_user_event.endpoint_callable = ( authentication_logout_user_event = Event( name="authentication_logout_user_event", key="g1j8i6j7-9k4h-0h6l-4i3j-2j0k1k0j0i0k", - request_validator=None, # TODO: Add request validator + request_validator=AuthenticationRequestModels.LogoutRequestModel, language_models=[], # response_validator=None, # TODO: Add response validator description="Logout user session", @@ -140,9 +140,9 @@ authentication_logout_user_event.endpoint_callable = ( authentication_refresher_token_event = Event( name="authentication_refresher_token_event", key="h2k9j7k8-0l5i-1i7m-5j4k-3k1l2l1k1j1l", - request_validator=None, # TODO: Add request validator + request_validator=AuthenticationRequestModels.RefresherRequestModel, # TODO: Add request validator language_models=[], - # response_validator=None, # TODO: Add response validator + # response_validator=None, description="Refresh authentication token", ) diff --git a/Events/AllEvents/authentication/auth/auth.py b/Events/AllEvents/authentication/auth/auth.py index b103f66..dc75e66 100644 --- a/Events/AllEvents/authentication/auth/auth.py +++ b/Events/AllEvents/authentication/auth/auth.py @@ -113,7 +113,7 @@ AuthenticationCheckTokenEventMethods = MethodToEvent( ) -def authentication_check_token_is_valid(): +def authentication_check_token_is_valid(request: Request): context_retriever = ContextRetrievers(func=authentication_check_token_is_valid) function = AuthenticationCheckTokenEventMethods.retrieve_event( event_function_code=f"{authentication_check_token_event.key}" @@ -142,7 +142,7 @@ AuthenticationRefreshEventMethods = MethodToEvent( ) -def authentication_refresh_user_info(): +def authentication_refresh_user_info(request: Request): context_retriever = ContextRetrievers(func=authentication_refresh_user_info) function = AuthenticationRefreshEventMethods.retrieve_event( event_function_code=f"{authentication_refresh_user_info_event.key}" @@ -169,7 +169,7 @@ AuthenticationChangePasswordEventMethods = MethodToEvent( ) -def authentication_change_password_event_callable(data: EndpointBaseRequestModel): +def authentication_change_password_event_callable(request: Request, data: EndpointBaseRequestModel): context_retriever = ContextRetrievers( func=authentication_change_password_event_callable ) @@ -198,7 +198,7 @@ AuthenticationCreatePasswordEventMethods = MethodToEvent( ) -def authentication_create_password(data: EndpointBaseRequestModel): +def authentication_create_password(request: Request, data: EndpointBaseRequestModel): context_retriever = ContextRetrievers(func=authentication_create_password) function = AuthenticationCreatePasswordEventMethods.retrieve_event( event_function_code=f"{authentication_create_password_event.key}" @@ -227,13 +227,18 @@ AuthenticationDisconnectUserEventMethods = MethodToEvent( ) -def authentication_disconnect_user(data: EndpointBaseRequestModel): +def authentication_disconnect_user(request: Request): context_retriever = ContextRetrievers(func=authentication_disconnect_user) function = AuthenticationDisconnectUserEventMethods.retrieve_event( event_function_code=f"{authentication_disconnect_user_event.key}" ) AuthenticationFunctions.context_retriever = context_retriever - return function.endpoint_callable(data=data) + return function.endpoint_callable() + + +AuthenticationDisconnectUserEventMethods.endpoint_callable = ( + authentication_disconnect_user +) AuthenticationLogoutEventMethods = MethodToEvent( @@ -249,13 +254,14 @@ AuthenticationLogoutEventMethods = MethodToEvent( ) -def authentication_logout_user(data: EndpointBaseRequestModel): +def authentication_logout_user(request: Request, data: EndpointBaseRequestModel): context_retriever = ContextRetrievers(func=authentication_logout_user) function = AuthenticationLogoutEventMethods.retrieve_event( event_function_code=f"{authentication_logout_user_event.key}" ) + validated_data = function.REQUEST_VALIDATOR(**data.data) AuthenticationFunctions.context_retriever = context_retriever - return function.endpoint_callable(data=data) + return function.endpoint_callable(data=validated_data) AuthenticationLogoutEventMethods.endpoint_callable = authentication_logout_user @@ -268,7 +274,7 @@ AuthenticationRefreshTokenEventMethods = MethodToEvent( }, headers=[], errors=[], - decorators_list=[MiddlewareModule.auth_required], + decorators_list=[], url="/refresh-token", method="POST", summary="Refresh token", @@ -276,13 +282,12 @@ AuthenticationRefreshTokenEventMethods = MethodToEvent( ) -def authentication_refresher_token(data: EndpointBaseRequestModel): - context_retriever = ContextRetrievers(func=authentication_refresher_token) +def authentication_refresher_token(request: Request, data: EndpointBaseRequestModel): function = AuthenticationRefreshTokenEventMethods.retrieve_event( event_function_code=f"{authentication_refresher_token_event.key}" ) - AuthenticationFunctions.context_retriever = context_retriever - return function.endpoint_callable(data=data) + validated_data = function.REQUEST_VALIDATOR(**data.data) + return function.endpoint_callable(request=request, data=validated_data) AuthenticationRefreshTokenEventMethods.endpoint_callable = ( @@ -304,7 +309,7 @@ AuthenticationForgotPasswordEventMethods = MethodToEvent( ) -def authentication_forgot_password(data: EndpointBaseRequestModel): +def authentication_forgot_password(request: Request, data: EndpointBaseRequestModel): context_retriever = ContextRetrievers(func=authentication_forgot_password) function = AuthenticationForgotPasswordEventMethods.retrieve_event( event_function_code=f"{authentication_forgot_password_event.key}" @@ -333,7 +338,7 @@ AuthenticationResetPasswordEventMethods = MethodToEvent( ) -def authentication_reset_password(data: EndpointBaseRequestModel): +def authentication_reset_password(request: Request, data: EndpointBaseRequestModel): context_retriever = ContextRetrievers(func=authentication_reset_password) function = AuthenticationResetPasswordEventMethods.retrieve_event( event_function_code=f"{authentication_reset_password_event.key}" @@ -362,7 +367,7 @@ AuthenticationDownloadAvatarEventMethods = MethodToEvent( ) -def authentication_download_avatar(): +def authentication_download_avatar(request: Request): context_retriever = ContextRetrievers(func=authentication_download_avatar) function = AuthenticationDownloadAvatarEventMethods.retrieve_event( event_function_code=f"{authentication_download_avatar_event.key}" diff --git a/Events/AllEvents/authentication/auth/function_handlers.py b/Events/AllEvents/authentication/auth/function_handlers.py index d825e6e..386ae6b 100644 --- a/Events/AllEvents/authentication/auth/function_handlers.py +++ b/Events/AllEvents/authentication/auth/function_handlers.py @@ -27,7 +27,9 @@ from ApiLayers.Schemas import ( Users, UsersTokens, ) -from Events.base_request_model import ContextRetrievers, TokenDictType +from Events.base_request_model import TokenDictType, BaseRouteModel +from Services.Redis.Actions.actions import RedisActions +from ApiLayers.AllConfigs.Redis.configs import RedisAuthKeys class Handlers: @@ -211,11 +213,9 @@ class Handlers: ) -class AuthenticationFunctions: +class AuthenticationFunctions(BaseRouteModel): """Class for handling authentication functions""" - context_retriever: Union[ContextRetrievers] = None - @classmethod # Requires no auth context def authentication_login_with_domain_and_creds(cls, request: Request, data: Any): """ @@ -292,13 +292,13 @@ class AuthenticationFunctions: return EndpointSuccessResponse( code="USER_INFO_REFRESHED", lang=cls.context_retriever.token.lang ).as_dict({ - "access_token": cls.context_retriever.token, "user": found_user.get_dict(), + "access_token": cls.context_retriever.get_token, "user": found_user.get_dict(), }) if not found_user: return EndpointNotAcceptableResponse( code="USER_NOT_FOUND", lang=cls.context_retriever.token.lang ).as_dict( - data={"user": found_user.get_dict()} + data={} ) @classmethod # Requires no auth context @@ -315,7 +315,7 @@ class AuthenticationFunctions: return EndpointNotAcceptableResponse( code="USER_NOT_FOUND", lang=cls.context_retriever.token.lang ).as_dict( - data={"user": found_user.get_dict()} + data={} ) @classmethod # Requires not auth context @@ -349,14 +349,18 @@ class AuthenticationFunctions: return EndpointNotAcceptableResponse( code="USER_NOT_FOUND", lang=cls.context_retriever.token.lang ).as_dict( - data={"user": found_user.get_dict()} + data={} ) registered_tokens = UsersTokens.filter_all( - UsersTokens.user_id == cls.context_retriever.token.id, db=db + UsersTokens.user_id == cls.context_retriever.token.user_id, db=db ) if registered_tokens.count: registered_tokens.query.delete() UsersTokens.save(db=db) + + RedisActions.delete( + list_keys=[f"{RedisAuthKeys.AUTH}:*:{str(found_user.uu_id)}"] + ) return EndpointSuccessResponse( code="DISCONNECTED_USER", lang=cls.context_retriever.token.lang ).as_dict(data={"user": found_user.get_dict()}) @@ -372,23 +376,32 @@ class AuthenticationFunctions: return EndpointNotAcceptableResponse( code="USER_NOT_FOUND", lang=cls.context_retriever.token.lang ).as_dict( - data={"user": found_user.get_dict()} + data={} ) registered_tokens = UsersTokens.filter_all_system( - UsersTokens.user_id == cls.context_retriever.token.id, + UsersTokens.user_id == cls.context_retriever.token.user_id, UsersTokens.domain == cls.context_retriever.token.domain, db=db, ) if registered_tokens.count: registered_tokens.query.delete() UsersTokens.save(db=db) + TokenService.remove_token_with_domain(user=found_user, domain=data.domain) return EndpointSuccessResponse( - code="DISCONNECTED_USER", lang=cls.context_retriever.token.lang + code="LOGOUT_USER", lang=cls.context_retriever.token.lang ).as_dict(data={"user": found_user.get_dict()}) @classmethod # Requires not auth context - def authentication_refresher_token(cls, data: Any): - """Refresh access token with refresher token""" + def authentication_refresher_token(cls, request: Request, data: Any): + """ + Refresh access token with refresher token + { + "data": { + "refresh_token": "string", + "domain": "string" + } + } + """ import arrow from ApiLayers.ApiServices.Token.token_handler import TokenService db = UsersTokens.new_session() @@ -397,15 +410,15 @@ class AuthenticationFunctions: domain=data.domain, db=db, ).data + language = request.headers.get("evyos-language", "tr") if not token_refresher: return EndpointNotAcceptableResponse( - code="REFRESHER_NOT_FOUND", lang=cls.context_retriever.token.lang + code="REFRESHER_NOT_FOUND", lang=language ).as_dict( data={"refresh_token": data.refresh_token} ) if found_user := Users.filter_one(Users.id == token_refresher.user_id, db=db).data: - request = cls.context_retriever.request token_created = TokenService.set_access_token_to_redis( request=request, user=found_user, @@ -420,9 +433,12 @@ class AuthenticationFunctions: "access_token": token_created.get("access_token"), "refresh_token": data.refresh_token, } - return EndpointSuccessResponse( - code="TOKEN_REFRESH", lang=cls.context_retriever.token.lang - ).as_dict(data=response_data) + return EndpointSuccessResponse(code="TOKEN_REFRESH", lang=language).as_dict(data=response_data) + raise EndpointNotAcceptableResponse( + code="USER_NOT_FOUND", lang=language + ).as_dict( + data={} + ) @classmethod # Requires not auth context def authentication_forgot_password(cls, data: Any): @@ -463,7 +479,7 @@ class AuthenticationFunctions: return cls.context_retriever.base @classmethod # Requires not auth context - def authentication_download_avatar(cls, data: Any): + def authentication_download_avatar(cls): """Download avatar icon and profile info of user""" import arrow db = Users.new_session() @@ -471,19 +487,19 @@ class AuthenticationFunctions: Users.id == cls.context_retriever.token.user_id, db=db ).data: expired_starts = str(arrow.now() - arrow.get(str(found_user.expiry_ends))) - expired_int = arrow.now() - arrow.get(str(found_user.expiry_ends)).days + expired_int = arrow.now().datetime - arrow.get(str(found_user.expiry_ends)).datetime user_info = { "lang": cls.context_retriever.token.lang, "full_name": found_user.person.full_name, "avatar": found_user.avatar, "remember_me": found_user.remember_me, "expiry_ends": str(found_user.expiry_ends), - "expired_str": expired_starts, - "expired_int": int(expired_int), + "expired_humanized": expired_starts, + "expired_day": int(expired_int.days) * -1, } return EndpointSuccessResponse( code="USER_AVATAR", lang=cls.context_retriever.token.lang ).as_dict(data=user_info) return EndpointNotAcceptableResponse( code="USER_NOT_FOUND", lang=cls.context_retriever.token.lang - ).as_dict(data={"user": found_user.get_dict()}) + ).as_dict(data={}) diff --git a/Events/AllEvents/authentication/auth/info.py b/Events/AllEvents/authentication/auth/info.py index dc58175..577eed9 100644 --- a/Events/AllEvents/authentication/auth/info.py +++ b/Events/AllEvents/authentication/auth/info.py @@ -7,5 +7,5 @@ authentication_page_info = PageInfo( description={"en": "Authentication"}, icon="", parent="", - url="", + url="" ) diff --git a/Events/AllEvents/authentication/auth/models.py b/Events/AllEvents/authentication/auth/models.py index b763d59..4e041ef 100644 --- a/Events/AllEvents/authentication/auth/models.py +++ b/Events/AllEvents/authentication/auth/models.py @@ -2,9 +2,11 @@ from ApiLayers.ApiValidations.Request import ( Login, EmployeeSelection, OccupantSelection, + Logout, CreatePassword, ChangePassword, Forgot, + Remember, ) @@ -13,6 +15,8 @@ class AuthenticationRequestModels: SelectCompanyOrOccupantTypeSuperUserRequestModel = { "EmployeeSelection": EmployeeSelection, "OccupantSelection": OccupantSelection, } + RefresherRequestModel = Remember + LogoutRequestModel = Logout class AuthenticationResponseModels: diff --git a/Events/Engine/abstract_class.py b/Events/Engine/abstract_class.py index 7c8131f..f308ee8 100644 --- a/Events/Engine/abstract_class.py +++ b/Events/Engine/abstract_class.py @@ -11,6 +11,7 @@ class PageInfo: BUTTON_NAME: str PAGE_URL: str PAGEINFO: Dict[str, Any] + URL: str = "" def __init__( self, @@ -18,13 +19,15 @@ class PageInfo: title: Dict[str, Any], description: Dict[str, Any], icon: str, - parent: str + parent: str, + url: str, ): self.NAME = name self.TITLE = title self.DESCRIPTION = description self.ICON = icon self.PARENT = parent + self.URL = url class Event: diff --git a/Events/base_request_model.py b/Events/base_request_model.py index b77356d..dda04d8 100644 --- a/Events/base_request_model.py +++ b/Events/base_request_model.py @@ -13,11 +13,10 @@ from ApiLayers.ApiValidations.Custom.token_objects import ( OccupantTokenObject, ) from ApiLayers.ApiValidations.Custom.wrapper_contexts import AuthContext, EventContext +from ApiLayers.AllConfigs.Token.config import Auth -TokenDictType = Union[ - EmployeeTokenObject, OccupantTokenObject -] # Type aliases for common types +TokenDictType = Union[EmployeeTokenObject, OccupantTokenObject] class EndpointBaseRequestModel(BaseModel): @@ -81,3 +80,13 @@ class ContextRetrievers: def base(self) -> Optional[dict[str, Any]]: """Retrieve base request model from a function.""" return getattr(self.context, "base", None) + + @property + def get_token(self) -> Optional[str]: + """Retrieve access key from a function.""" + return getattr(self.request, "headers", {}).get(Auth.ACCESS_TOKEN_TAG, None) + + +class BaseRouteModel: + + context_retriever: Union[ContextRetrievers] = None diff --git a/Services/Redis/Models/access.py b/Services/Redis/Models/access.py index c886b7a..1c55ee8 100644 --- a/Services/Redis/Models/access.py +++ b/Services/Redis/Models/access.py @@ -29,7 +29,7 @@ class AccessToken(BaseRedisModel): @property def count(self): - return 2 + return 3 @property def delimiter(self):