diff --git a/auth_app/views.py b/auth_app/views.py index 444287b..fd40eaa 100644 --- a/auth_app/views.py +++ b/auth_app/views.py @@ -3,14 +3,30 @@ from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status, authentication from rest_framework.authtoken.models import Token -from rest_framework.authtoken.views import ObtainAuthToken from django.contrib.auth import authenticate +from join.settings import TOKEN_EXPIRATION_TIME from .serializer import LoginSerializer from rest_framework.permissions import IsAuthenticated +from django.db import models +from django.utils import timezone + class LoginView(APIView): serializer_class = LoginSerializer + def _create_token_response(self, user): + token, created = ExpiringToken.objects.get_or_create(user=user) + + if not created and token.is_expired(): + token.delete() + token = ExpiringToken.objects.create(user=user) + + if created or not token.expires_at: + token.expires_at = timezone.now() + TOKEN_EXPIRATION_TIME + token.save() + + return Response({'token': token.key, 'user_id': user.id}, status=status.HTTP_200_OK) + def post(self, request): serializer = self.serializer_class(data=request.data) @@ -27,26 +43,44 @@ class LoginView(APIView): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - def _create_token_response(self, user): - token, created = Token.objects.get_or_create(user=user) - return Response({'token': token.key, 'user_id' : user.id}, status=status.HTTP_200_OK) - class LogoutView(APIView): authentication_classes = [authentication.TokenAuthentication] permission_classes = [IsAuthenticated] def post(self, request): - try: - request.user.auth_token.delete() - return Response({"message": "Successfully logged out"}, status=status.HTTP_200_OK) - except Exception as e: - return Response({"error": "Logout failed"}, status=status.HTTP_400_BAD_REQUEST) + token = getattr(request.user, 'auth_token', None) + + if token is None: + return Response({"error": "No active session or token already expired"}, status=status.HTTP_400_BAD_REQUEST) + + token.delete() + return Response({"message": "Successfully logged out"}, status=status.HTTP_200_OK) class AuthView(APIView): authentication_classes = [authentication.TokenAuthentication] def get(self, request): - if request.user.is_authenticated: - return Response({"user_id": request.user.id}, status=status.HTTP_200_OK) - return Response({"error": "User is not logged in"}, status=status.HTTP_401_UNAUTHORIZED) + if not request.user.is_authenticated: + return Response({"error": "User is not logged in"}, status=status.HTTP_401_UNAUTHORIZED) + try: + token = ExpiringToken.objects.get(user=request.user) + except ExpiringToken.DoesNotExist: + return Response({"error": "Token does not exist, please log in again"}, status=status.HTTP_401_UNAUTHORIZED) + + if token.is_expired(): + token.delete() + return Response({"error": "Token expired, please log in again"}, status=status.HTTP_401_UNAUTHORIZED) + + return Response({"user_id": request.user.id}, status=status.HTTP_200_OK) + +class ExpiringToken(Token): + expires_at = models.DateTimeField(null=True, blank=True) + + def is_expired(self): + return self.expires_at and self.expires_at < timezone.now() + + def save(self, *args, **kwargs): + if not self.expires_at: + self.expires_at = timezone.now() + TOKEN_EXPIRATION_TIME + super().save(*args, **kwargs) \ No newline at end of file diff --git a/join/settings.py b/join/settings.py index 70082e3..5f6e0fe 100644 --- a/join/settings.py +++ b/join/settings.py @@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.1/ref/settings/ """ from pathlib import Path +from datetime import timedelta # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -150,6 +151,11 @@ STATIC_URL = 'static/' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +# Token + +AUTH_TOKEN_MODEL = 'auth_app.ExpiringToken' + +TOKEN_EXPIRATION_TIME = timedelta(hours=4) # Authentication