From 62287dcfa24e05084a45aadb4b49d26ef1d32c95 Mon Sep 17 00:00:00 2001 From: mtoof Date: Sun, 25 Aug 2024 01:46:06 +0300 Subject: [PATCH 1/6] fixed some issues in tokenHandler.js to return correct data, fixed issues in token-service views.py where I did not use .first() for user profile in refresh views and added some loggers --- .../game_history/game_history/settings.py | 111 ++++++-------- Backend/token_service/README.md | 11 +- .../token_service/token_app/views.py | 48 +++--- .../token_service/token_service/settings.py | 94 ++++++------ .../user_service/user_app/GameRoomConsumer.py | 20 +-- .../user_app/OnlineStatusConsumer.py | 143 +----------------- .../user_service/user_app/views.py | 7 +- .../user_service/user_service/settings.py | 2 +- Frontend/src/js/modals/profile.js | 10 ++ Frontend/src/js/tokenHandler.js | 7 +- 10 files changed, 145 insertions(+), 308 deletions(-) diff --git a/Backend/game_history/game_history/game_history/settings.py b/Backend/game_history/game_history/game_history/settings.py index a944a22..0412de5 100755 --- a/Backend/game_history/game_history/game_history/settings.py +++ b/Backend/game_history/game_history/game_history/settings.py @@ -19,53 +19,53 @@ PGSQL_HOST = os.environ.get('PGSQL_HOST') DB_USER = os.environ.get('DB_USER') DB_PASS = os.environ.get('DB_PASS') -LOG_DIR = Path('/var/log/') - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'format': '{levelname} {asctime} {module} {message}', - 'style': '{', - }, - 'simple': { - 'format': '{levelname} {message}', - 'style': '{', - }, - }, - 'handlers': { - 'file': { - 'level': 'DEBUG', - 'class': 'logging.FileHandler', - 'filename': os.path.join(LOG_DIR, 'django_debug.log'), - 'formatter': 'verbose', - }, - 'error_file': { - 'level': 'ERROR', - 'class': 'logging.FileHandler', - 'filename': os.path.join(LOG_DIR, 'django_error.log'), - 'formatter': 'verbose', - }, - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'simple', - }, - }, - 'loggers': { - 'django': { - 'handlers': ['file', 'console'], - 'level': 'DEBUG', - 'propagate': True, - }, - 'django.request': { - 'handlers': ['error_file'], - 'level': 'ERROR', - 'propagate': False, - }, - }, -} +# LOG_DIR = Path('/var/log/') + +# LOGGING = { +# 'version': 1, +# 'disable_existing_loggers': False, +# 'formatters': { +# 'verbose': { +# 'format': '{levelname} {asctime} {module} {message}', +# 'style': '{', +# }, +# 'simple': { +# 'format': '{levelname} {message}', +# 'style': '{', +# }, +# }, +# 'handlers': { +# 'file': { +# 'level': 'DEBUG', +# 'class': 'logging.FileHandler', +# 'filename': os.path.join(LOG_DIR, 'django_debug.log'), +# 'formatter': 'verbose', +# }, +# 'error_file': { +# 'level': 'ERROR', +# 'class': 'logging.FileHandler', +# 'filename': os.path.join(LOG_DIR, 'django_error.log'), +# 'formatter': 'verbose', +# }, +# 'console': { +# 'level': 'DEBUG', +# 'class': 'logging.StreamHandler', +# 'formatter': 'simple', +# }, +# }, +# 'loggers': { +# 'django': { +# 'handlers': ['file', 'console'], +# 'level': 'DEBUG', +# 'propagate': True, +# }, +# 'django.request': { +# 'handlers': ['error_file'], +# 'level': 'ERROR', +# 'propagate': False, +# }, +# }, +# } # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -99,26 +99,9 @@ 'django.contrib.staticfiles', 'game_data', 'rest_framework', - 'rest_framework_simplejwt', 'corsheaders', ] -SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60), - "REFRESH_TOKEN_LIFETIME": timedelta(days=1), - "ROTATE_REFRESH_TOKENS": False, # If True, refresh tokens will rotate, meaning that a new token is returned with each request to the refresh endpoint. for example, if a user is logged in on multiple devices, rotating refresh tokens will cause all devices to be logged out when the user logs out on one device. - "BLACKLIST_AFTER_ROTATION": True, # If True, the refresh token will be blacklisted after it is used to obtain a new access token. This means that if a refresh token is stolen, it can only be used once to obtain a new access token. This is useful if rotating refresh tokens is enabled, but can cause problems if a refresh token is shared between multiple clients. - "AUTH_HEADER_TYPES": ("Bearer",), - "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), -} - -# Add REST framework settings for JWT authentication -REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework_simplejwt.authentication.JWTAuthentication', - ), -} - MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", diff --git a/Backend/token_service/README.md b/Backend/token_service/README.md index 8cf6d96..f0e2429 100644 --- a/Backend/token_service/README.md +++ b/Backend/token_service/README.md @@ -16,11 +16,18 @@ The API runs on port 8000 and exposed to 8001. ## Tutorial to use the token_service There are three endpoints in the token_service. The endpoints are: -- `auth/token/refresh/` - This endpoint is used to refresh the access token. to refresh the access token you need to send a request to this endpoint with the refresh token in the request body. The request will be like this: +- `auth/token/refresh/` - This endpoint is used to refresh the access token. to refresh the access token you need to send a request to this endpoint with the refresh token in the request body. +You should send the "user refresh token" in the header as brearer token and The request will be like this: + ```json { "id": "user_id", - "refresh": "your refresh token" +} +``` +It will return the new access token as a response. +```json +{ + "access": "new access token" } ``` diff --git a/Backend/token_service/token_service/token_app/views.py b/Backend/token_service/token_service/token_app/views.py index 2f6b98a..ecbb04d 100644 --- a/Backend/token_service/token_service/token_app/views.py +++ b/Backend/token_service/token_service/token_app/views.py @@ -63,8 +63,6 @@ def post(self, request, *args, **kwargs) -> Response: else: try: user, create = UserTokens.objects.get_or_create(id=id, username=username) - # logger.info('user= %s', user.username) - # logger.info('create= %s', create) if create: refresh = RefreshToken.for_user(user) access_token = str(refresh.access_token) @@ -81,7 +79,8 @@ def post(self, request, *args, **kwargs) -> Response: else: token_data = user.token_data try: - refresh = RefreshToken(token_data["refresh"]) + refresh = RefreshToken.for_user(user) + token_data["refresh"] = str(refresh) token_data["access"] = str(refresh.access_token) user.token_data = token_data user.save() @@ -90,18 +89,13 @@ def post(self, request, *args, **kwargs) -> Response: "refresh": str(refresh), "access": token_data["access"] } - except jwt.ExpiredSignatureError: - response_message = {"erro": "User session has expired, You must login again"} - status_code = status.HTTP_401_UNAUTHORIZED + except Exception as err: response_message = {"error": str(err)} status_code = status.HTTP_400_BAD_REQUEST except Exception as err: response_message = {"error": str(err)} status_code = status.HTTP_500_INTERNAL_SERVER_ERROR - - # logger.info('response_message = %s', response_message) - # logger.info('status_code = %s', status_code) return Response(response_message, status=status_code) class CustomTokenRefreshView(TokenRefreshView): @@ -127,19 +121,22 @@ def post(self, request, *args, **kwargs) -> Response: user_id = request.data.get("id") if not user_id: return Response({"error": "User id is required"}, status=status.HTTP_400_BAD_REQUEST) - user_object = UserTokens.objects.filter(id=user_id) + user_object = UserTokens.objects.filter(id=user_id).first() if not user_object: return Response({"error": "User not found"}, status=status.HTTP_404_NOT_FOUND) token_data = user_object.token_data refresh_token = bearer.split(' ')[1] refresh = RefreshToken(refresh_token) access_token = str(refresh.access_token) - token_data["access"] = str(refresh.access_token) + token_data["access"] = access_token user_object.token_data=token_data user_object.save() - return Response({"access": access_token}, status=status.HTTP_200_OK) + except jwt.ExpiredSignatureError: + response_message = {"error": "User session has expired, You must login again"} + status_code = status.HTTP_401_UNAUTHORIZED except Exception as err: return Response({"error": "Could not generate access token", "details": str(err)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return Response({"access": access_token}, status=status.HTTP_200_OK) class ValidateToken(viewsets.ViewSet): @@ -171,10 +168,8 @@ def validate_token_for_user(self, request, *args, **kwargs): isfrontend = request.data.get("is_frontend") if isfrontend is None: response_message, status_code = check_secret(request, response_message, status_code) - if "error" not in response_message and status_code == status.HTTP_200_OK: + if not response_message: try: - status_code = status.HTTP_200_OK - response_message = {} access = request.data.get("access") id = request.data.get("id") if not access or not id: @@ -182,36 +177,31 @@ def validate_token_for_user(self, request, *args, **kwargs): status_code = status.HTTP_400_BAD_REQUEST result = self.validate_token(access) if result: - # logger.info("result= %s", result) user = UserTokens.objects.filter(id = id, token_data__access = access).first() - - # logger.info("user.username= %s", user.username) - # logger.info("user.token_data['access']= %s", user.token_data["access"]) - if result: + if user is not None: response_message = {"access_token": "Valid token"} else: response_message = {"error": "token mismatch"} status_code = status.HTTP_401_UNAUTHORIZED - except jwt.ExpiredSignatureError: - response_message = {"error": "token is expired"} - status_code = status.HTTP_401_UNAUTHORIZED - except jwt.InvalidTokenError: - response_message = {"error": "Invalid token"} - status_code = status.HTTP_401_UNAUTHORIZED + else: + response_message = {"error": "Invalid or expired token"} + status_code = status.HTTP_401_UNAUTHORIZED except Http404: response_message = {"error": "User has not logged in yet!!"} status_code = status.HTTP_401_UNAUTHORIZED except Exception as err: - response_message = {"error": str(err)} + response_message = {"error": "Could not validate user access token"} status_code = status.HTTP_500_INTERNAL_SERVER_ERROR - # logger.info("response_message= %s", response_message) + return Response(response_message, status=status_code) class InvalidateToken(viewsets.ViewSet): def invalidate_token_for_user(self, request, *args, **kwargs) -> Response: response_message = {} status_code = status.HTTP_200_OK - response_message, status_code = check_secret(request, response_message, status_code) + isfrontend = request.data.get("is_frontend") + if isfrontend is None: + response_message, status_code = check_secret(request, response_message, status_code) if "error" not in response_message and status_code == status.HTTP_200_OK: try: access = request.data.get("access") diff --git a/Backend/token_service/token_service/token_service/settings.py b/Backend/token_service/token_service/token_service/settings.py index 454abe2..89286e0 100644 --- a/Backend/token_service/token_service/token_service/settings.py +++ b/Backend/token_service/token_service/token_service/settings.py @@ -22,53 +22,53 @@ DB_USER = os.environ.get('DB_USER') DB_PASS = os.environ.get('DB_PASS') -LOG_DIR = Path('/var/log/') - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'format': '{levelname} {asctime} {module} {message}', - 'style': '{', - }, - 'simple': { - 'format': '{levelname} {message}', - 'style': '{', - }, - }, - 'handlers': { - 'file': { - 'level': 'DEBUG', - 'class': 'logging.FileHandler', - 'filename': os.path.join(LOG_DIR, 'django_debug.log'), - 'formatter': 'verbose', - }, - 'error_file': { - 'level': 'ERROR', - 'class': 'logging.FileHandler', - 'filename': os.path.join(LOG_DIR, 'django_error.log'), - 'formatter': 'verbose', - }, - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'simple', - }, - }, - 'loggers': { - 'django': { - 'handlers': ['file', 'console'], - 'level': 'DEBUG', - 'propagate': True, - }, - 'django.request': { - 'handlers': ['error_file'], - 'level': 'ERROR', - 'propagate': False, - }, - }, -} +# LOG_DIR = Path('/var/log/') + +# LOGGING = { +# 'version': 1, +# 'disable_existing_loggers': False, +# 'formatters': { +# 'verbose': { +# 'format': '{levelname} {asctime} {module} {message}', +# 'style': '{', +# }, +# 'simple': { +# 'format': '{levelname} {message}', +# 'style': '{', +# }, +# }, +# 'handlers': { +# 'file': { +# 'level': 'DEBUG', +# 'class': 'logging.FileHandler', +# 'filename': os.path.join(LOG_DIR, 'django_debug.log'), +# 'formatter': 'verbose', +# }, +# 'error_file': { +# 'level': 'ERROR', +# 'class': 'logging.FileHandler', +# 'filename': os.path.join(LOG_DIR, 'django_error.log'), +# 'formatter': 'verbose', +# }, +# 'console': { +# 'level': 'DEBUG', +# 'class': 'logging.StreamHandler', +# 'formatter': 'simple', +# }, +# }, +# 'loggers': { +# 'django': { +# 'handlers': ['file', 'console'], +# 'level': 'DEBUG', +# 'propagate': True, +# }, +# 'django.request': { +# 'handlers': ['error_file'], +# 'level': 'ERROR', +# 'propagate': False, +# }, +# }, +# } # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent diff --git a/Backend/user_service/user_service/user_app/GameRoomConsumer.py b/Backend/user_service/user_service/user_app/GameRoomConsumer.py index 38ec3f8..a4d27e6 100644 --- a/Backend/user_service/user_service/user_app/GameRoomConsumer.py +++ b/Backend/user_service/user_service/user_app/GameRoomConsumer.py @@ -6,9 +6,12 @@ from django.utils.decorators import method_decorator import logging import requests +from django.conf import settings logger = logging.getLogger(__name__) +GAME_HISTORY_URL = settings.GAME_HISTORY_URL + @method_decorator(csrf_exempt, name='dispatch') class GameRoomConsumer(AsyncWebsocketConsumer): async def connect(self): @@ -31,17 +34,14 @@ async def connect(self): ) await self.accept() if room_obj is None: - logger.info('Room obj is None') await self.close(4001) return await self.send_notification("broadcast_message", 'entered room', self.scope['user'].username) asyncio.sleep(1) res = await self.check_room_players() - logger.info('res = %s', res) if res: asyncio.sleep(1) response = await self.create_and_send_game_history_record() - logger.info('Response = %s', response) await self.send_notification('starting_game', response, self.scope['user'].username) # Extract token from Query string @@ -80,10 +80,6 @@ def set_room_and_player(self, player): else: return None gameroom_obj.save() - #TODO:remove it - from .serializers import GameRoomSerializer - serializer = GameRoomSerializer(gameroom_obj) - logger.info("data = %s", serializer.data) return gameroom_obj async def disconnect(self, code): @@ -97,7 +93,6 @@ async def disconnect(self, code): @database_sync_to_async def remove_from_room(self): - from .serializers import GameRoomSerializer from .models import GameRoom room_obj = GameRoom.objects.get(room_name = self.room_name) @@ -111,11 +106,7 @@ def remove_from_room(self): room_obj.save() if room_obj.player1 is None and room_obj.player2 is None: room_obj.delete() - logger.info('Room got deleted') return - serializer = GameRoomSerializer(room_obj) - if serializer is not None: - logger.info('Room = %s', serializer.data) async def send_notification(self, type, message, user): await self.channel_layer.group_send( @@ -153,10 +144,10 @@ def create_and_send_game_history_record(self): request = {} response = {} gameroom_obj = GameRoom.objects.get(room_name=self.room_name) + # Check if both players are present in the room and create a game history record if they are present if gameroom_obj is not None and gameroom_obj.player1 is not None and gameroom_obj.player2 is not None: serializer = GameRoomSerializer(gameroom_obj).data if serializer["player1_id"] and serializer["player2_id"]: - logger.info('serilizer = %s', serializer) request = { "player1_id":serializer["player1_id"], "player1_username":serializer["player1_username"], @@ -164,8 +155,7 @@ def create_and_send_game_history_record(self): "player2_username": serializer["player2_username"], "start_time": now() } - response = requests.post('http://game-history:8002/game-history/', data=request) - logger.info('Response = %s', response.json()) + response = requests.post(f'${GAME_HISTORY_URL}/game-history/', data=request) return response.json() @database_sync_to_async diff --git a/Backend/user_service/user_service/user_app/OnlineStatusConsumer.py b/Backend/user_service/user_service/user_app/OnlineStatusConsumer.py index 0374826..1191dc6 100644 --- a/Backend/user_service/user_service/user_app/OnlineStatusConsumer.py +++ b/Backend/user_service/user_service/user_app/OnlineStatusConsumer.py @@ -27,9 +27,7 @@ async def connect(self): self.channel_name ) await self.accept() - await self.change_online_status(self.scope['user'], 'open') self.user_channels[self.scope['user'].username] = self.channel_name - # print(f'Connected to WebSocket: {self.room_group_name}') await self.add_player_to_lobby(self.scope['user']) # Extract token from Query string @@ -163,149 +161,10 @@ async def send_onlineStatus(self, event): except Exception as e: print(f'Error in send_onlineStatus: {e}') - - @database_sync_to_async - def change_online_status(self, username, c_type): - from .models import UserProfileModel as User - - try: - userprofile = User.objects.get(username=username) - # print(f'User profile username: {userprofile.username}') - if c_type == 'open': - userprofile.online_status = True - userprofile.save() - else: - userprofile.online_status = False - userprofile.save() - # print(f'Changed status for {username} to {c_type}') - except User.DoesNotExist: - print(f'User {username} does not exist.') - except User.DoesNotExist: - print(f'User profile for {username} does not exist.') - except Exception as e: - print(f'Error changing status: {e}') - async def disconnect(self, code): await self.channel_layer.group_discard( self.room_group_name, self.channel_name ) - await self.change_online_status(self.scope['user'], 'close') - print(f'Disconnected from WebSocket: {self.room_group_name} with code: {code}') - # if self.scope['user'] in self.waiting_list: - # self.waiting_list.remove(self.scope['user']) - - - # # Match two players - # async def match_players(self, player1, player2): - # await self.channel_layer.send( - # self.user_channels[player1.username], - # { - # 'type': 'match_found', - # 'message': f'Match found with {player2.username}', - # 'player': player2.username - # } - # ) - # await self.channel_layer.send( - # self.user_channels[player2.username], - # { - # 'type': 'match_found', - # 'message': f'Match found with {player1.username}', - # 'player': player1.username - # } - # ) - - # # Send match players notifications - # async def match_found(self, event): - # message = event['message'] - # player = event['player'] - # # Handle the match found event here - # await self.send(text_data=json.dumps({ - # 'message': message, - # 'player': player - # })) - - # async def wait_for_responses(self, player1, player2): - # loop = asyncio.get_event_loop() - # tasks = [ - # loop.create_task(self.await_response(player1)), - # loop.create_task(self.await_response(player2)) - # ] - # done, pending = await asyncio.wait(tasks, timeout=5) - - # if pending: - # for task in pending: - # task.cancel() - # await self.send_timeout_notification(player1, player2) - # logger.info(f"Match response timed out for {player1.username} and {player2.username}") - - # # Wait for players response - # async def await_response(self, player): - # try: - # self.response_queues[player.username] = asyncio.Queue() - # response = await self.response_queues[player.username].get() - # logger.info(f"Received response from {player.username}: {response}") - # # Handle response here - # except asyncio.CancelledError: - # logger.info(f"Cancelled waiting for {player.username}'s response") - - # Handle match accepted from users - # async def handle_match_accepted(self, data): - # data_type = data["type"] - # player = self.scope["user"] - # target = data["player"] - # await self.channel_layer.send( - # self.user_channels[target], - # { - # 'type': 'match_accepted', - # 'message': f'{player.username} accepted the match', - # } - # ) - - # # match accepted function - # async def match_accepted(self, event): - # message = event['message'] - # await self.send(text_data=json.dumps({ - # 'message': message, - # })) - - # # Handle match rejected from users - # async def handle_match_rejected(self, data): - # data_type = data["type"] - # player = self.scope["user"] - # target = data["player"] - # await self.channel_layer.send( - # self.user_channels[target], - # { - # 'type': 'match_rejected', - # 'message': f'{player.username} rejected the match', - # } - # ) - - # async def match_rejected(self, event): - # message = event['message'] - # await self.send(text_data=json.dumps({ - # 'message': message, - # })) - - # async def send_timeout_notification(self, player1, player2): - # await self.channel_layer.send( - # self.user_channels[player1.username], - # { - # 'type': 'match_timeout', - # 'message': f'Match response timed out', - # } - # ) - # await self.channel_layer.send( - # self.user_channels[player2.username], - # { - # 'type': 'match_timeout', - # 'message': f'Match response timed out', - # } - # ) - # async def match_timeout(self, event): - # message = event['message'] - # await self.send(text_data=json.dumps({ - # 'message': message, - # })) \ No newline at end of file + print(f'Disconnected from WebSocket: {self.room_group_name} with code: {code}') \ No newline at end of file diff --git a/Backend/user_service/user_service/user_app/views.py b/Backend/user_service/user_service/user_app/views.py index 05d1ce1..de93ec6 100644 --- a/Backend/user_service/user_service/user_app/views.py +++ b/Backend/user_service/user_service/user_app/views.py @@ -20,13 +20,12 @@ from .user_session_views import generate_secret import logging import requests -import os - -load_dotenv() +from django.conf import settings -TOEKNSERVICE = os.environ.get('TOKEN_SERVICE') +TOEKNSERVICE = settings.TOKEN_SERVICE_URL logger = logging.getLogger(__name__) + headers = { "X-SERVICE-SECRET": settings.SECRET_KEY # Replace with your actual secret key } diff --git a/Backend/user_service/user_service/user_service/settings.py b/Backend/user_service/user_service/user_service/settings.py index 951eef0..fd41954 100644 --- a/Backend/user_service/user_service/user_service/settings.py +++ b/Backend/user_service/user_service/user_service/settings.py @@ -14,7 +14,6 @@ from datetime import timedelta from pathlib import Path from dotenv import load_dotenv -import re load_dotenv() TOKEN_SERVICE_URL = os.environ.get('TOKEN_SERVICE') @@ -22,6 +21,7 @@ PGSQL_HOST = os.environ.get('PGSQL_HOST') DB_USER = os.environ.get('DB_USER') DB_PASS = os.environ.get('DB_PASS') + LOG_DIR = Path('/var/log/') LOGGING = { diff --git a/Frontend/src/js/modals/profile.js b/Frontend/src/js/modals/profile.js index 9edb0a0..de5f325 100644 --- a/Frontend/src/js/modals/profile.js +++ b/Frontend/src/js/modals/profile.js @@ -144,7 +144,17 @@ export function updateUserProfile() { .catch(error => { console.error('Error verifying token:', error); }); + document.getElementById('friendsButton').addEventListener('click', updateFriendsList); + handleTokenVerification() + .then(validToken => { + userData.token = validToken; + updateFriendsList(userData); + }) + .catch(error => { + console.error('Error verifying token:', error); + }); + document.getElementById('matchHistoryButton').addEventListener('click', updateMatchHistory); // Handle 2FA Toggle Switch document.getElementById('twoFactorAuthToggle').addEventListener('change', function () { diff --git a/Frontend/src/js/tokenHandler.js b/Frontend/src/js/tokenHandler.js index 732f5ba..33938df 100644 --- a/Frontend/src/js/tokenHandler.js +++ b/Frontend/src/js/tokenHandler.js @@ -4,7 +4,7 @@ function refreshTokenRequest(userData) { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${userData.refreshToken}` + 'Authorization': `Bearer ${userData.refresh}` }, body: JSON.stringify({ id: userData.id }) }) @@ -16,10 +16,9 @@ function refreshTokenRequest(userData) { }) .then(data => { // Update userData with new tokens - userData.token = data.token; - userData.refreshToken = data.refreshToken; + userData.token = data.access; sessionStorage.setItem('userData', JSON.stringify(userData)); - return data.token; + return userData.token; }); } From e5ccc179b3442bbaf33ee1217e50aa64fa34b558 Mon Sep 17 00:00:00 2001 From: Abbas Toof Date: Sun, 25 Aug 2024 16:12:10 +0300 Subject: [PATCH 2/6] added some comments for our functions and methods in token_service and user_service and removed unused headers --- .../token_service/token_app/views.py | 22 ++++++++++--------- .../user_service/user_app/serializers.py | 11 +++++----- .../user_app/user_session_views.py | 13 +++++------ .../user_service/user_app/views.py | 11 ++++------ 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/Backend/token_service/token_service/token_app/views.py b/Backend/token_service/token_service/token_app/views.py index ecbb04d..08c7c39 100644 --- a/Backend/token_service/token_service/token_app/views.py +++ b/Backend/token_service/token_service/token_app/views.py @@ -37,8 +37,8 @@ class CustomTokenObtainPairView(TokenObtainPairView): start_consumer: Method to start the RabbitMQ consumer. """ permission_classes = [AllowAny] - serializer_class = CustomTokenObtainPairSerializer - def post(self, request, *args, **kwargs) -> Response: + serializer_class = CustomTokenObtainPairSerializer # this is the serializer class that is used to generate the token for the user + def post(self, request, *args, **kwargs) -> Response: # login endpoint will send the user id and username to generate a refresh token and access token for the user """ Create a new token for the user. @@ -99,7 +99,7 @@ def post(self, request, *args, **kwargs) -> Response: return Response(response_message, status=status_code) class CustomTokenRefreshView(TokenRefreshView): - def post(self, request, *args, **kwargs) -> Response: + def post(self, request, *args, **kwargs) -> Response: # Frontend will send the refresh token in the request header as Bearer token and the user id in the request body to generate a new access token """ Post method to generate new access token using refresh token. @@ -155,6 +155,11 @@ def validate_token(self, access_token) -> bool: bool: True if the token is valid, False otherwise. """ try: + ''' decode the token to check if it is expired + and if it is expired, it will raise an exception + and if it is invalid, it will raise an exception + jwt.decode() method is used to decode the token and check if it is expired or invalid + ''' decoded_token = jwt.decode(access_token, settings.SECRET_KEY, algorithms=["HS256"], options={"verify_signature": True}) return True except jwt.ExpiredSignatureError: @@ -196,7 +201,7 @@ def validate_token_for_user(self, request, *args, **kwargs): return Response(response_message, status=status_code) class InvalidateToken(viewsets.ViewSet): - def invalidate_token_for_user(self, request, *args, **kwargs) -> Response: + def invalidate_token_for_user(self, request, *args, **kwargs) -> Response: # logout endpoint will send the user id and access token in the request body to delete the user token record from the database response_message = {} status_code = status.HTTP_200_OK isfrontend = request.data.get("is_frontend") @@ -216,12 +221,9 @@ def invalidate_token_for_user(self, request, *args, **kwargs) -> Response: if user is not None: user.delete() response_message = {"detail":"User logged out"} - except jwt.ExpiredSignatureError: - response_message = {"error": "Access token is expired"} - status_code = status.HTTP_401_UNAUTHORIZED - except jwt.InvalidTokenError: - response_message = {"error": "Invalid access token"} - status_code = status.HTTP_401_UNAUTHORIZED + else: + response_message = {"error": "Invalid or expired token"} + status_code = status.HTTP_401_UNAUTHORIZED except Http404: response_message = {"error": "User has not logged in yet"} status_code = status.HTTP_401_UNAUTHORIZED diff --git a/Backend/user_service/user_service/user_app/serializers.py b/Backend/user_service/user_service/user_app/serializers.py index 24613e8..c7aede8 100644 --- a/Backend/user_service/user_service/user_app/serializers.py +++ b/Backend/user_service/user_service/user_app/serializers.py @@ -1,7 +1,6 @@ from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError from rest_framework import serializers -from rest_framework.validators import UniqueValidator from .models import UserProfileModel, FriendRequest, GameRoom, ConfirmEmail from .validators import CustomPasswordValidator @@ -122,15 +121,15 @@ def update(self, instance, validated_data) -> UserProfileModel: serializers.ValidationError: If the password is the same as the current password. """ - for attr, value in validated_data.items(): - if attr == "password" and value is not None: - if instance.check_password(value): + for attr, value in validated_data.items(): # Iterate over the validated data and update the user object + if attr == "password" and value is not None: # Check if the attribute is password and the value is not None (i.e., the password is being updated) + if instance.check_password(value): # Check if the new password is the same as the current password (i.e., the user is trying to set the same password) raise serializers.ValidationError(detail="New password must be different from the current password.") # Validate the new password using CustomPasswordValidator try: - validator = CustomPasswordValidator() - validator.validate(value, user=instance) + validator = CustomPasswordValidator() # Create an instance of CustomPasswordValidator + validator.validate(value, user=instance) # Validate the new password using CustomPasswordValidator except ValidationError as err: raise serializers.ValidationError(detail=err.messages) from err instance.set_password(value) diff --git a/Backend/user_service/user_service/user_app/user_session_views.py b/Backend/user_service/user_service/user_app/user_session_views.py index b573849..8403e25 100644 --- a/Backend/user_service/user_service/user_app/user_session_views.py +++ b/Backend/user_service/user_service/user_app/user_session_views.py @@ -61,22 +61,22 @@ def login(self, request): if user.is_active: serializer = UserSerializer(user) if user.otp_status: + # 2FA is enabled otp = generate_secret() user.otp = make_password(str(otp)) user.otp_expiry_time = now() + timedelta(minutes=3) user.save() - logger.info('user email = %s', serializer.data["email"]) self.send_email(serializer.data["email"], otp) response_message = {"detail":"Verification password sent to your email"} status_code = status.HTTP_200_OK else: + # 2FA is disabled data = {"id": serializer.data["id"], "username": serializer.data["username"]} response = requests.post(f"{TOEKNSERVICE}/auth/token/gen-tokens/", data=data, headers=headers) if response.status_code == 201: user.online_status = True user.save() response_message = response.json() - # logger.info('user_data = %s', response.json()) if "error" in response_message: status_code = response_message.get("status_code") response_message = response.json() @@ -91,8 +91,7 @@ def login(self, request): status_code = status.HTTP_400_BAD_REQUEST return Response(response_message, status=status_code) - #TODO: use check password for verify - def verify_otp(self, request): + def verify_otp(self, request): # frontend will send username, password and otp to verify the otp and login the user if the otp is correct status_code = status.HTTP_200_OK response = {} response_message = {} @@ -101,8 +100,8 @@ def verify_otp(self, request): otp = request.data.get("otp") if username and password and otp: user = self.authenticate_user(request, username, password) - if user is not None: - if user.otp_status: + if user is not None: # if the user is authenticated + if user.otp_status: # if the user has enabled 2FA if check_password(str(otp), user.otp): if user.otp_expiry_time > now(): data = {"id": user.id, "username": username} @@ -113,7 +112,7 @@ def verify_otp(self, request): user.otp_expiry_time = None user.online_status = True user.save() - # logger.info('user_data = %s', response_message) + if "error" in response_message: status_code = response_message.get("status_code") else: diff --git a/Backend/user_service/user_service/user_app/views.py b/Backend/user_service/user_service/user_app/views.py index de93ec6..3c9cd71 100644 --- a/Backend/user_service/user_service/user_app/views.py +++ b/Backend/user_service/user_service/user_app/views.py @@ -1,7 +1,5 @@ -import json -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404 from django.http import Http404 -from django.core.exceptions import ValidationError as DjangoValidationError from rest_framework.exceptions import ValidationError from rest_framework import status, viewsets from rest_framework.permissions import AllowAny, IsAuthenticated @@ -12,11 +10,10 @@ from rest_framework.parsers import MultiPartParser, FormParser from rest_framework.decorators import parser_classes from django.utils.timezone import now, timedelta -from .serializers import UserSerializer, FriendSerializer, ConfirmEmailSerializer +from .serializers import UserSerializer, FriendSerializer from django.core.mail import send_mail from django.conf import settings from django.db.models import Q -from dotenv import load_dotenv from .user_session_views import generate_secret import logging import requests @@ -27,7 +24,7 @@ logger = logging.getLogger(__name__) headers = { - "X-SERVICE-SECRET": settings.SECRET_KEY # Replace with your actual secret key + "X-SERVICE-SECRET": settings.SECRET_KEY } def extract_token(request): @@ -156,7 +153,7 @@ def update_user(self, request, pk=None) -> Response: return Response({'error': item_lists}, status=status.HTTP_400_BAD_REQUEST) except Exception as err: return Response({"error": str(err)}, status=status.HTTP_400_BAD_REQUEST) - + def handle_email(self, data, user_obj): response_message = {} status_code = status.HTTP_200_OK From b25d2aa7c0e62595ef309da55d55ea36597e4ad2 Mon Sep 17 00:00:00 2001 From: Abbas Toof Date: Sun, 25 Aug 2024 16:56:22 +0300 Subject: [PATCH 3/6] removed total_hits from gamestate class and also added player1_hits and player2_hits instead and modified the same thing in tests --- .../game_history/game_data/models.py | 6 ++-- .../game_history/tests/test_game_history.py | 31 ++++++++++++------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Backend/game_history/game_history/game_data/models.py b/Backend/game_history/game_history/game_data/models.py index 94741d4..4591134 100755 --- a/Backend/game_history/game_history/game_data/models.py +++ b/Backend/game_history/game_history/game_data/models.py @@ -18,8 +18,10 @@ def __str__(self): class GameStat(models.Model): game_id = models.OneToOneField(GameHistory, on_delete=models.CASCADE, primary_key=True) # this field is a foreign key to the GameHistory model player1_score = models.IntegerField() + player1_hits = models.IntegerField() player2_score = models.IntegerField() - total_hits = models.IntegerField() + player2_hits = models.IntegerField() longest_rally = models.IntegerField() + def __str__(self): - return f"Stats for Game {self.game_id.game_id}: {self.player1_score} vs {self.player2_score} - Total Hits: {self.total_hits}, Longest Rally: {self.longest_rally}" + return (f"Stats for Game {self.game_id.game_id}: {self.player1_score} vs {self.player2_score} - Longest Rally: {self.longest_rally}") diff --git a/Backend/game_history/game_history/game_history/tests/test_game_history.py b/Backend/game_history/game_history/game_history/tests/test_game_history.py index 6f481fa..253e604 100755 --- a/Backend/game_history/game_history/game_history/tests/test_game_history.py +++ b/Backend/game_history/game_history/game_history/tests/test_game_history.py @@ -142,7 +142,8 @@ def test_create_game_stat(api_client): 'game_id': game.game_id, 'player1_score': 10, 'player2_score': 5, - 'total_hits': 15, + 'player1_hits': 0, + 'player2_hits': 2, 'longest_rally': 4 } response = api_client.post(url, data, format='json') @@ -151,16 +152,17 @@ def test_create_game_stat(api_client): game_stat = GameStat.objects.first() assert game_stat.player1_score == 10 assert game_stat.player2_score == 5 - assert game_stat.total_hits == 15 + assert game_stat.player1_hits == 0 + assert game_stat.player2_hits == 2 assert game_stat.longest_rally == 4 @pytest.mark.django_db def test_list_game_stat(api_client): game1 = GameHistory.objects.create(player1_id=1, player2_id=2, winner_id=1, start_time=now()) - GameStat.objects.create(game_id=game1, player1_score=10, player2_score=5, total_hits=15, longest_rally=4) + GameStat.objects.create(game_id=game1, player1_score=10, player2_score=5, player1_hits=0, player2_hits=2, longest_rally=4) game2 = GameHistory.objects.create(player1_id=3, player2_id=4, winner_id=4, start_time=now()) - GameStat.objects.create(game_id=game2, player1_score=8, player2_score=7, total_hits=20, longest_rally=5) + GameStat.objects.create(game_id=game2, player1_score=8, player2_score=7, player1_hits=20, player2_hits=0, longest_rally=5) url = reverse('gamestat-list') response = api_client.get(url, format='json') @@ -168,37 +170,41 @@ def test_list_game_stat(api_client): assert len(response.data) == 2 assert response.data[0]['player1_score'] == 10 assert response.data[0]['player2_score'] == 5 - assert response.data[0]['total_hits'] == 15 + assert response.data[0]['player1_hits'] == 0 + assert response.data[0]['player2_hits'] == 2 assert response.data[0]['longest_rally'] == 4 assert response.data[1]['player1_score'] == 8 assert response.data[1]['player2_score'] == 7 - assert response.data[1]['total_hits'] == 20 + assert response.data[1]['player1_hits'] == 20 + assert response.data[1]['player2_hits'] == 0 assert response.data[1]['longest_rally'] == 5 @pytest.mark.django_db def test_retrieve_game_stat(api_client): game = GameHistory.objects.create(player1_id=1, player2_id=2, winner_id=1, start_time=now()) - game_stat = GameStat.objects.create(game_id=game, player1_score=10, player2_score=5, total_hits=15, longest_rally=4) + game_stat = GameStat.objects.create(game_id=game, player1_score=10, player2_score=5, player1_hits=15, player2_hits=2, longest_rally=4) url = reverse('gamestat-detail', args=[game_stat.pk]) response = api_client.get(url, format='json') assert response.status_code == status.HTTP_200_OK assert response.data['player1_score'] == game_stat.player1_score assert response.data['player2_score'] == game_stat.player2_score - assert response.data['total_hits'] == game_stat.total_hits + assert response.data['player1_hits'] == game_stat.player1_hits + assert response.data['player2_hits'] == game_stat.player2_hits assert response.data['longest_rally'] == game_stat.longest_rally @pytest.mark.django_db def test_update_game_stat(api_client): game = GameHistory.objects.create(player1_id=1, player2_id=2, winner_id=1, start_time=now()) - game_stat = GameStat.objects.create(game_id=game, player1_score=10, player2_score=5, total_hits=15, longest_rally=4) + game_stat = GameStat.objects.create(game_id=game, player1_score=10, player2_score=5, player1_hits=15, player2_hits=2, longest_rally=4) url = reverse('gamestat-detail', args=[game_stat.pk]) data = { 'game_id': game.game_id, 'player1_score': 12, 'player2_score': 6, - 'total_hits': 18, + 'player1_hits': 18, + 'player2_hits': 0, 'longest_rally': 5 } response = api_client.put(url, data, format='json') @@ -206,13 +212,14 @@ def test_update_game_stat(api_client): game_stat.refresh_from_db() assert game_stat.player1_score == 12 assert game_stat.player2_score == 6 - assert game_stat.total_hits == 18 + assert game_stat.player1_hits == 18 + assert game_stat.player2_hits == 0 assert game_stat.longest_rally == 5 @pytest.mark.django_db def test_delete_game_stat(api_client): game = GameHistory.objects.create(player1_id=1, player2_id=2, winner_id=1, start_time=now()) - game_stat = GameStat.objects.create(game_id=game, player1_score=10, player2_score=5, total_hits=15, longest_rally=4) + game_stat = GameStat.objects.create(game_id=game, player1_score=10, player2_score=5, player1_hits=15, player2_hits=2, longest_rally=4) url = reverse('gamestat-detail', args=[game_stat.pk]) response = api_client.delete(url) From fbdbb8efde97034dbc12ded76357a27ef6f4b364 Mon Sep 17 00:00:00 2001 From: Pooria Toof Date: Sun, 25 Aug 2024 17:10:07 +0300 Subject: [PATCH 4/6] modified Readme for game_history and startGame-overview.md in doc directroy --- Backend/game_history/README.md | 3 ++- docs/startGame-overview.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Backend/game_history/README.md b/Backend/game_history/README.md index 7402e06..fc3b7ba 100644 --- a/Backend/game_history/README.md +++ b/Backend/game_history/README.md @@ -64,7 +64,8 @@ The `GameStat` model represents the statistics of a game, linked to a `GameHisto - `game_id`: OneToOneField, primary key linked to `GameHistory`. - `player1_score`: Integer, score of the first player. - `player2_score`: Integer, score of the second player. -- `total_hits`: Integer, total number of hits in the game. +- `player1_hits`: Integer, number of hits by the first player. +- `player2_hits`: Integer, number of hits by the second player. - `longest_rally`: Integer, the longest rally in the game. ### Serializers diff --git a/docs/startGame-overview.md b/docs/startGame-overview.md index 96c64b5..d0c5f47 100644 --- a/docs/startGame-overview.md +++ b/docs/startGame-overview.md @@ -74,7 +74,8 @@ Example JSON data: "winner": 42, "player1_score": 10, "player2_score": 1, - "total_hits": 999, + "player1_hits": 100, + "player2_hits": 50, "longest_rally": 15, "game_duration": 300 } From 7dd86ece5d6b7053aec26118481b47f30b78dac8 Mon Sep 17 00:00:00 2001 From: Ville Mustonen Date: Sun, 25 Aug 2024 19:42:57 +0300 Subject: [PATCH 5/6] tokens working, when refresh token expires we force logout but there is a bug with page history and url update --- .../token_service/token_service/settings.py | 2 +- Frontend/src/js/modals/changeEmail.js | 73 ++++---- Frontend/src/js/modals/changePassword.js | 17 +- .../src/js/modals/changeProfilePicture.js | 58 +++--- Frontend/src/js/modals/login.js | 2 +- Frontend/src/js/modals/profile.js | 96 ++++------ Frontend/src/js/modals/tournament.js | 168 +++++++++++------- Frontend/src/js/tokenHandler.js | 7 +- 8 files changed, 221 insertions(+), 202 deletions(-) diff --git a/Backend/token_service/token_service/token_service/settings.py b/Backend/token_service/token_service/token_service/settings.py index 89286e0..c5dbf5c 100644 --- a/Backend/token_service/token_service/token_service/settings.py +++ b/Backend/token_service/token_service/token_service/settings.py @@ -109,7 +109,7 @@ SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(minutes=1), - "REFRESH_TOKEN_LIFETIME": timedelta(days=1), + "REFRESH_TOKEN_LIFETIME": timedelta(minutes=1), "ROTATE_REFRESH_TOKENS": False, "BLACKLIST_AFTER_ROTATION": True, "AUTH_HEADER_TYPES": ("Bearer",), diff --git a/Frontend/src/js/modals/changeEmail.js b/Frontend/src/js/modals/changeEmail.js index 38a38ac..63879eb 100644 --- a/Frontend/src/js/modals/changeEmail.js +++ b/Frontend/src/js/modals/changeEmail.js @@ -1,4 +1,5 @@ import { showMessage } from './messages.js'; +import { handleTokenVerification } from '../tokenHandler.js'; // Import your token verification function // Function to toggle the email update form visibility export function toggleEmailForm() { @@ -27,15 +28,17 @@ export function handleEmailUpdate(userData) { console.error('Email cannot be empty'); return; } - console.log('New email:', newEmail); - // Check if the new email is available - fetch('/user/register/availableuser/', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${userData.token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ email: newEmail }) + + handleTokenVerification().then(token => { + // Check if the new email is available + return fetch('/user/register/availableuser/', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ email: newEmail }) + }); }) .then(response => { if (!response.ok) { @@ -45,13 +48,15 @@ export function handleEmailUpdate(userData) { }) .then(() => { // Send OTP to the new email - return fetch('/user/register/sendemailotp/', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${userData.token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ email: newEmail }) + return handleTokenVerification().then(token => { + return fetch('/user/register/sendemailotp/', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ email: newEmail }) + }); }); }) .then(response => { @@ -81,14 +86,16 @@ export function handleEmailUpdate(userData) { return; } - // Verify the OTP - fetch('/user/register/verifyemailotp/', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${userData.token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ email: newEmail, otp: otp }) + handleTokenVerification().then(token => { + // Verify the OTP + return fetch('/user/register/verifyemailotp/', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ email: newEmail, otp: otp }) + }); }) .then(response => { if (!response.ok) { @@ -98,13 +105,15 @@ export function handleEmailUpdate(userData) { }) .then(() => { // Update the email in the backend - return fetch(`/user/${userData.id}/`, { - method: 'PATCH', - headers: { - 'Authorization': `Bearer ${userData.token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ email: newEmail }) + return handleTokenVerification().then(token => { + return fetch(`/user/${userData.id}/`, { + method: 'PATCH', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ email: newEmail }) + }); }); }) .then(response => { @@ -132,4 +141,4 @@ export function handleEmailUpdate(userData) { document.getElementById('emailVerificationForm').style.display = 'none'; document.getElementById('updateEmailForm').style.display = 'flex'; }); -} \ No newline at end of file +} diff --git a/Frontend/src/js/modals/changePassword.js b/Frontend/src/js/modals/changePassword.js index 11e61ac..e619041 100644 --- a/Frontend/src/js/modals/changePassword.js +++ b/Frontend/src/js/modals/changePassword.js @@ -1,4 +1,5 @@ import { showMessage } from './messages.js'; +import { handleTokenVerification } from '../tokenHandler.js'; // Import your token verification function // Function to toggle the password update form visibility export function togglePasswordForm() { @@ -21,13 +22,15 @@ export function handlePasswordUpdate(userData) { return; } - fetch(`/user/${userData.id}/`, { - method: 'PATCH', - headers: { - 'Authorization': `Bearer ${userData.token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ password: newPassword }) + handleTokenVerification().then(token => { + return fetch(`/user/${userData.id}/`, { + method: 'PATCH', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ password: newPassword }) + }); }) .then(response => { if (!response.ok) { diff --git a/Frontend/src/js/modals/changeProfilePicture.js b/Frontend/src/js/modals/changeProfilePicture.js index 6f2da04..b456914 100644 --- a/Frontend/src/js/modals/changeProfilePicture.js +++ b/Frontend/src/js/modals/changeProfilePicture.js @@ -1,4 +1,5 @@ - import { showMessage } from './messages.js'; +import { showMessage } from './messages.js'; +import { handleTokenVerification } from '../tokenHandler.js';// Import your token verification function // Function to toggle the profile picture update form visibility export function toggleProfilePictureForm() { @@ -35,32 +36,39 @@ export function handleProfilePictureUpdate(userData) { const formData = new FormData(); formData.append('avatar', file); - fetch(`/user/${userData.id}/`, { - method: 'PATCH', - headers: { - 'Authorization': `Bearer ${userData.token}` - }, - body: formData - }) - .then(response => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then(data => { - console.log('Image uploaded successfully:', data); - showMessage('Profile picture updated successfully', '#ProfileModal', 'accept'); - document.getElementById('avatar').src = `${data.avatar}?t=${new Date().getTime()}`; - document.getElementById('imageUploadForm').style.display = 'none'; - document.getElementById('imageInput').value = ''; - document.getElementById('fileName').textContent = 'No file chosen'; + // Verify token and update it if necessary + handleTokenVerification().then(token => { + fetch(`/user/${userData.id}/`, { + method: 'PATCH', + headers: { + 'Authorization': `Bearer ${token}` + }, + body: formData + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + console.log('Image uploaded successfully:', data); + showMessage('Profile picture updated successfully', '#ProfileModal', 'accept'); + document.getElementById('avatar').src = `${data.avatar}?t=${new Date().getTime()}`; + document.getElementById('imageUploadForm').style.display = 'none'; + document.getElementById('imageInput').value = ''; + document.getElementById('fileName').textContent = 'No file chosen'; + }) + .catch(error => { + console.error('Error uploading image:', error); + showMessage('Error uploading image', '#ProfileModal', 'error'); + document.getElementById('imageInput').value = ''; + document.getElementById('fileName').textContent = 'No file chosen'; + }); }) .catch(error => { - console.error('Error uploading image:', error); - showMessage('Error uploading image', '#ProfileModal', 'error'); - document.getElementById('imageInput').value = ''; - document.getElementById('fileName').textContent = 'No file chosen'; + console.error('Error handling token verification:', error); + showMessage('Session expired. Please log in again.', '#ProfileModal', 'error'); }); }); } diff --git a/Frontend/src/js/modals/login.js b/Frontend/src/js/modals/login.js index 10a1fbd..4a34530 100644 --- a/Frontend/src/js/modals/login.js +++ b/Frontend/src/js/modals/login.js @@ -131,7 +131,7 @@ export function confirmLogout() { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': 'Bearer ' + userData.token + 'Authorization': 'Bearer ' + token } }) .then(response => { diff --git a/Frontend/src/js/modals/profile.js b/Frontend/src/js/modals/profile.js index de5f325..9038d59 100644 --- a/Frontend/src/js/modals/profile.js +++ b/Frontend/src/js/modals/profile.js @@ -113,59 +113,20 @@ export function updateUserProfile() { // Initialize event handlers document.getElementById('changeEmailButton').addEventListener('click', toggleEmailForm); - handleTokenVerification() - .then(validToken => { - userData.token = validToken; - handleEmailUpdate(userData); - - }) - .catch(error => { - console.error('Error verifying token:', error); - }); + handleEmailUpdate(userData); document.getElementById('changePasswordButton').addEventListener('click', togglePasswordForm); - - handleTokenVerification() - .then(validToken => { - userData.token = validToken; - handlePasswordUpdate(userData); - }) - .catch(error => { - console.error('Error verifying token:', error); - }); + handlePasswordUpdate(userData); document.getElementById('changeProfilePictureButton').addEventListener('click', toggleProfilePictureForm); - - handleTokenVerification() - .then(validToken => { - userData.token = validToken; - handleProfilePictureUpdate(userData); - }) - .catch(error => { - console.error('Error verifying token:', error); - }); + handleProfilePictureUpdate(userData); + document.getElementById('friendsButton').addEventListener('click', updateFriendsList); - handleTokenVerification() - .then(validToken => { - userData.token = validToken; - updateFriendsList(userData); - }) - .catch(error => { - console.error('Error verifying token:', error); - }); document.getElementById('matchHistoryButton').addEventListener('click', updateMatchHistory); // Handle 2FA Toggle Switch document.getElementById('twoFactorAuthToggle').addEventListener('change', function () { - - handleTokenVerification() - .then(validToken => { - userData.token = validToken; - toggleTwoFactorAuth(userData, this.checked, userData.token); - }) - .catch(error => { - console.error('Error verifying token:', error); - }); + toggleTwoFactorAuth(userData, this.checked); }); }) @@ -184,28 +145,31 @@ export function updateUserProfile() { }); // Function to toggle 2FA status on the server - function toggleTwoFactorAuth(userData, isEnabled, token) { - fetch(`/user/${userData.id}/`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` - }, - body: JSON.stringify({ otp_status: isEnabled ? "True" : "False" }) - }) - .then(response => { - if (!response.ok) { - throw new Error('Failed to update 2FA status'); - } - return response.json(); - }) - .then(data => { - console.log('2FA status updated successfully:', data.otp_status); - showMessage('2FA status updated successfully', '#ProfileModal', 'accept'); - console.log('2FA status updated successfully:', data); - }) - .catch(error => { - console.error('Error updating 2FA status:', error); + function toggleTwoFactorAuth(userData, isEnabled) { + handleTokenVerification() + .then(validToken => { + return fetch(`/user/${userData.id}/`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${validToken}` + }, + body: JSON.stringify({ otp_status: isEnabled ? "True" : "False" }) }); + }) + .then(response => { + if (!response.ok) { + throw new Error('Failed to update 2FA status'); + } + return response.json(); + }) + .then(data => { + console.log('2FA status updated successfully:', data.otp_status); + showMessage('2FA status updated successfully', '#ProfileModal', 'accept'); + }) + .catch(error => { + console.error('Error updating 2FA status:', error); + showMessage('Error updating 2FA status', '#ProfileModal', 'error'); + }); } } diff --git a/Frontend/src/js/modals/tournament.js b/Frontend/src/js/modals/tournament.js index 70b2cfd..90b75cb 100644 --- a/Frontend/src/js/modals/tournament.js +++ b/Frontend/src/js/modals/tournament.js @@ -3,6 +3,9 @@ import { startGame, endGame } from '../pong/pong.js'; import GameSession from '../pong/classes/GameSession.js'; document.addEventListener('DOMContentLoaded', async () => { + + console.log("ALKU"); // pois + const playerForm = document.getElementById('playerForm'); const playerAliasInputs = document.getElementById('playerAliasInputs'); const tournamentModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('tournamentModal')); @@ -34,8 +37,23 @@ document.addEventListener('DOMContentLoaded', async () => { } }); + if (sessionStorage.getItem('pause') === 'true') + gameInfoModal.hide(); + if (sessionStorage.getItem('pause2') === 'true') { + sessionStorage.setItem('remainingIDs', sessionStorage.getItem('remainingIDsTmp')); + sessionStorage.setItem('roundWinners', sessionStorage.getItem('roundWinnersTmp')); + pongModal.hide(); + } + + // tournamentStages || 0 = reset || 1 = before first game || 2 = all other games except final || 3 = final + if(parseInt(sessionStorage.getItem('tournamentStages'))) // TESTI + tournamentLogic(); + // else + // sessionStorage.setItem('tournamentStages', '0'); + function resetTournament() { + sessionStorage.setItem('isGameOver', 'true'); randomNamesButton.style.display = 'none'; @@ -47,7 +65,14 @@ document.addEventListener('DOMContentLoaded', async () => { sessionStorage.setItem('tournamentPlayers', JSON.stringify([])); sessionStorage.setItem('remainingIDs', JSON.stringify([])); sessionStorage.setItem('roundWinners', JSON.stringify([])); + sessionStorage.setItem('remainingIDsTmp', JSON.stringify([])); + sessionStorage.setItem('roundWinnersTmp', JSON.stringify([])); + + sessionStorage.setItem('tournamentStages', '0'); + sessionStorage.setItem('pause', 'false'); + sessionStorage.setItem('pause2', 'false'); + tournamentModal.hide(); pongModal.hide(); gameInfoModal.hide(); @@ -68,38 +93,10 @@ document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('keydown', function (event) { if (event.key === "Escape" || event.keyCode === 27) { resetTournament(); - console.log("ESC PRESSED!"); + console.log("ESC PRESSED!"); // pois } }); - function resetTournament() { - - sessionStorage.setItem('isGameOver', 'true'); - - randomNamesButton.style.display = 'none'; - startTournamentButton.style.display = 'none'; - playerForm.reset(); - playerAliasInputs.innerHTML = ''; - - sessionStorage.setItem('infoScreen', 'false'); - sessionStorage.setItem('tournamentPlayers', JSON.stringify([])); - sessionStorage.setItem('remainingIDs', JSON.stringify([])); - sessionStorage.setItem('roundWinners', JSON.stringify([])); - - tournamentModal.hide(); - pongModal.hide(); - gameInfoModal.hide(); - - console.log("TOURNAMENT RESET"); // pois - } - - closeButtons.forEach((closeButton) => { - closeButton.addEventListener('click', () => { - console.log("MODAL X PRESSED"); // pois - resetTournament(); - }); - }); - gameInfoButton.addEventListener('click', function () { sessionStorage.setItem('infoScreen', 'false'); }) @@ -206,6 +203,8 @@ document.addEventListener('DOMContentLoaded', async () => { sessionStorage.setItem('remainingIDs', JSON.stringify(shuffledNumbers)); // start tournament + sessionStorage.setItem('tournamentStages', '1'); + console.log("tournamentStage Set to 1"); // pois tournamentLogic(); } else { playerForm.classList.add('was-validated'); @@ -215,36 +214,54 @@ document.addEventListener('DOMContentLoaded', async () => { async function tournamentLogic() { let remainingIDs = JSON.parse(sessionStorage.getItem('remainingIDs')); + let roundWinners = []; let winnerName = []; let tmpPlayerOne = []; let tmpPlayerTwo = []; - tmpPlayerOne = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === remainingIDs[0])); - tmpPlayerTwo = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === remainingIDs[1])); - - document.getElementById('winner').textContent = []; - document.getElementById('nextPlayers').textContent = ("Next Players: " + tmpPlayerOne.name + " and " + tmpPlayerTwo.name); - - sessionStorage.setItem('infoScreen', 'true'); - gameInfoModal.show(); - while (sessionStorage.getItem('infoScreen') === 'true') - await new Promise(resolve => setTimeout(resolve, 100)); - gameInfoModal.hide(); + if(parseInt(sessionStorage.getItem('tournamentStages')) === 1) { + tmpPlayerOne = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === remainingIDs[0])); + tmpPlayerTwo = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === remainingIDs[1])); + + document.getElementById('winner').textContent = []; + document.getElementById('nextPlayers').textContent = ("Next Players: " + tmpPlayerOne.name + " and " + tmpPlayerTwo.name); + + sessionStorage.setItem('infoScreen', 'true'); + gameInfoModal.show(); + while (sessionStorage.getItem('infoScreen') === 'true') + await new Promise(resolve => setTimeout(resolve, 100)); + gameInfoModal.hide(); + + sessionStorage.setItem('remainingIDsTmp', sessionStorage.getItem('remainingIDs')); + sessionStorage.setItem('roundWinnersTmp', sessionStorage.getItem('roundWinners')); + + sessionStorage.setItem('pause2', 'true'); + startNextGame(); + while (sessionStorage.getItem('isGameOver') === 'false') + await new Promise(resolve => setTimeout(resolve, 100)); + sessionStorage.setItem('pause2', 'false'); + pongModal.hide(); + sessionStorage.setItem('tournamentStages', '2'); + console.log("tournamentStage Set to 2"); // pois + } - while(remainingIDs.length !== 2) - { - while(remainingIDs.length !== 0) + if(parseInt(sessionStorage.getItem('tournamentStages')) === 2) { + console.log("Stage 2 alkaa"); // pois + while(1) { - startNextGame(); - while (sessionStorage.getItem('isGameOver') === 'false') - await new Promise(resolve => setTimeout(resolve, 100)); - pongModal.hide(); - remainingIDs = JSON.parse(sessionStorage.getItem('remainingIDs')); roundWinners = JSON.parse(sessionStorage.getItem('roundWinners')); - winnerName = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === roundWinners[roundWinners.length - 1])); + if(remainingIDs.length === 0 && roundWinners.length === 1) { + console.log("TESTI BREAK"); + break ; + } + + if(roundWinners.length === 0) + winnerName = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === remainingIDs[remainingIDs.length - 1])); + else + winnerName = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === roundWinners[roundWinners.length - 1])); if(remainingIDs.length !== 0) { tmpPlayerOne = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === remainingIDs[0])); @@ -255,37 +272,51 @@ document.addEventListener('DOMContentLoaded', async () => { tmpPlayerTwo = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === roundWinners[1])); } - if(!winnerName) { return ; } - document.getElementById('winner').textContent = ("Last game winner: " + winnerName.name); document.getElementById('nextPlayers').textContent = ("Next Players: " + tmpPlayerOne.name + " and " + tmpPlayerTwo.name); sessionStorage.setItem('infoScreen', 'true'); gameInfoModal.show(); + sessionStorage.setItem('pause', 'true'); while (sessionStorage.getItem('infoScreen') === 'true') await new Promise(resolve => setTimeout(resolve, 100)); + sessionStorage.setItem('pause', 'false'); gameInfoModal.hide(); + + if(remainingIDs.length === 0 && roundWinners.length !== 0) { + remainingIDs = JSON.parse(sessionStorage.getItem('roundWinners')); + sessionStorage.setItem('roundWinners', JSON.stringify([])); + sessionStorage.setItem('remainingIDs', JSON.stringify(remainingIDs)); + } + + sessionStorage.setItem('remainingIDsTmp', sessionStorage.getItem('remainingIDs')); + sessionStorage.setItem('roundWinnersTmp', sessionStorage.getItem('roundWinners')); + + sessionStorage.setItem('pause2', 'true'); + startNextGame(); + while (sessionStorage.getItem('isGameOver') === 'false') + await new Promise(resolve => setTimeout(resolve, 100)); + sessionStorage.setItem('pause2', 'false'); + pongModal.hide(); } - remainingIDs = JSON.parse(sessionStorage.getItem('roundWinners')); - sessionStorage.setItem('roundWinners', JSON.stringify([])); - sessionStorage.setItem('remainingIDs', JSON.stringify(remainingIDs)); + sessionStorage.setItem('tournamentStages', '3'); + console.log("tournamentStage Set to 3"); // pois } - startNextGame(); - while (sessionStorage.getItem('isGameOver') === 'false') - await new Promise(resolve => setTimeout(resolve, 100)); - pongModal.hide(); - roundWinners = JSON.parse(sessionStorage.getItem('roundWinners')); - winnerName = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === roundWinners[roundWinners.length - 1])); - document.getElementById('winner').textContent = ("Tournament Winner: " + winnerName.name); - document.getElementById('nextPlayers').textContent = []; + if(parseInt(sessionStorage.getItem('tournamentStages'))) { + roundWinners = JSON.parse(sessionStorage.getItem('roundWinners')); + winnerName = JSON.parse(sessionStorage.getItem('tournamentPlayers')).find((player) => (player.id === roundWinners[roundWinners.length - 1])); - sessionStorage.setItem('infoScreen', 'true'); - gameInfoModal.show(); - while (sessionStorage.getItem('infoScreen') === 'true') - await new Promise(resolve => setTimeout(resolve, 100)); - gameInfoModal.hide(); - resetTournament(); // ehka + document.getElementById('winner').textContent = ("Tournament Winner: " + winnerName.name); + document.getElementById('nextPlayers').textContent = []; + + sessionStorage.setItem('infoScreen', 'true'); + gameInfoModal.show(); + while (sessionStorage.getItem('infoScreen') === 'true') + await new Promise(resolve => setTimeout(resolve, 100)); + gameInfoModal.hide(); + resetTournament(); + } } function startNextGame() { @@ -309,7 +340,6 @@ document.addEventListener('DOMContentLoaded', async () => { currentGame++; const newRemainingIds = remainingIDs.slice(2); sessionStorage.setItem('remainingIDs', JSON.stringify(newRemainingIds)); - sessionStorage.setItem('remainingIDs', JSON.stringify(newRemainingIds)); pongModal.show(); startGame('pongGameContainer', config, gameResultCallBack); diff --git a/Frontend/src/js/tokenHandler.js b/Frontend/src/js/tokenHandler.js index 33938df..2116fd3 100644 --- a/Frontend/src/js/tokenHandler.js +++ b/Frontend/src/js/tokenHandler.js @@ -1,3 +1,5 @@ +import { updateAuthButton } from "./modals/buttons"; + // Function to refresh the token function refreshTokenRequest(userData) { return fetch('/auth/token/refresh/', { @@ -10,7 +12,10 @@ function refreshTokenRequest(userData) { }) .then(response => { if (!response.ok) { - throw new Error('Token refresh failed'); + alert('Session expired. Please login again.'); + sessionStorage.clear(); + updateAuthButton(false); + window.location.replace('https://localhost:80/#login'); } return response.json(); }) From b8efe361f2dba27209ee053ebea75a865b637d72 Mon Sep 17 00:00:00 2001 From: mtoof Date: Sun, 25 Aug 2024 19:46:17 +0300 Subject: [PATCH 6/6] added delete previous avatar image if it's not default.jpg and added some comments --- Backend/user_service/user_service/user_app/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Backend/user_service/user_service/user_app/views.py b/Backend/user_service/user_service/user_app/views.py index 3c9cd71..65f2ba8 100644 --- a/Backend/user_service/user_service/user_app/views.py +++ b/Backend/user_service/user_service/user_app/views.py @@ -112,7 +112,7 @@ def retrieve_user(self, request, pk=None) -> Response: except Exception as err: return Response({"error": str(err)}, status=status.HTTP_400_BAD_REQUEST) - @parser_classes([MultiPartParser, FormParser]) + @parser_classes([MultiPartParser, FormParser]) # we need to add this decorator to handle file uploads def update_user(self, request, pk=None) -> Response: """ Method to update a user. @@ -138,6 +138,9 @@ def update_user(self, request, pk=None) -> Response: if "email" in data: response_message, status_code = self.handle_email(data, user_obj) if not response_message: + if "avatar" in data: + if user_obj.avatar != "default.jpg": + user_obj.avatar.delete(save=False) serializer = UserSerializer(instance=user_obj, data=data, partial=True) serializer.is_valid(raise_exception=True) serializer.save()