feat: implement API security measures phase 1
This commit is contained in:
+73
-1
@@ -6,8 +6,16 @@ from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import AnonRateThrottle
|
||||
from rest_framework_simplejwt.exceptions import TokenError
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
|
||||
from accounts.serializers import LoginSerializer, RegisterSerializer, UserSerializer
|
||||
from accounts.serializers import (
|
||||
LoginSerializer,
|
||||
LogoutSerializer,
|
||||
RefreshRequestSerializer,
|
||||
RegisterSerializer,
|
||||
UserSerializer,
|
||||
)
|
||||
|
||||
|
||||
class AuthRateThrottle(AnonRateThrottle):
|
||||
@@ -46,4 +54,68 @@ def login_view(request: Request) -> Response:
|
||||
"refresh": validated_data["refresh"],
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
@permission_classes([AllowAny])
|
||||
@throttle_classes([AuthRateThrottle])
|
||||
def token_refresh_view(request: Request) -> Response:
|
||||
"""Refresh an access token using a refresh token.
|
||||
|
||||
Uses SimpleJWT's built-in rotation and blacklisting.
|
||||
"""
|
||||
serializer = RefreshRequestSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
refresh_token_str: str = serializer.validated_data["refresh"]
|
||||
try:
|
||||
refresh = RefreshToken(refresh_token_str)
|
||||
access = str(refresh.access_token)
|
||||
new_refresh = str(refresh)
|
||||
except TokenError as e:
|
||||
return Response(
|
||||
{"error": str(e), "code": "token_invalid"},
|
||||
status=status.HTTP_401_UNAUTHORIZED,
|
||||
)
|
||||
|
||||
return Response(
|
||||
{"access": access, "refresh": new_refresh},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
@permission_classes([AllowAny])
|
||||
def logout_view(request: Request) -> Response:
|
||||
"""Blacklist a refresh token (log out).
|
||||
|
||||
This allows explicit token revocation on logout.
|
||||
"""
|
||||
serializer = LogoutSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
refresh_token_str: str = serializer.validated_data["refresh"]
|
||||
try:
|
||||
refresh = RefreshToken(refresh_token_str)
|
||||
refresh.blacklist()
|
||||
except TokenError:
|
||||
# If token is already invalid/blacklisted, still consider logout successful
|
||||
pass
|
||||
except AttributeError:
|
||||
# If blacklist app not installed
|
||||
pass
|
||||
|
||||
return Response(
|
||||
{"message": "Successfully logged out."},
|
||||
status=status.HTTP_205_RESET_CONTENT,
|
||||
)
|
||||
|
||||
|
||||
@api_view(["GET"])
|
||||
def me_view(request: Request) -> Response:
|
||||
"""Return the current authenticated user's profile."""
|
||||
return Response(
|
||||
UserSerializer(request.user).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
Reference in New Issue
Block a user