Commit dbee2adc authored by Ilham Maulana's avatar Ilham Maulana 💻

fix: shifting auth api to simple jwt

parent 91432e03
from rest_framework import permissions from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.tokens import RefreshToken
class IsStaffUser(permissions.BasePermission): class IsStaffUser(IsAuthenticated):
def has_permission(self, request, view): def has_permission(self, request, view):
if request.method != "POST" and not request.user.is_staff: refresh_token = request.session.get("refresh_token")
return False
elif request.method != "POST" and not request.user.is_authenticated:
return False
return True return bool(
refresh_token is not None
and request.user
and request.user.is_authenticated
and request.user.is_staff
)
class IsNotStaffUser(permissions.BasePermission): class IsNotStaffUser(IsAuthenticated):
def has_permission(self, request, view): def has_permission(self, request, view):
if request.method != "POST" and request.user.is_staff: refresh_token = request.session.get("refresh_token")
return False return bool(
elif request.method != "POST" and not request.user.is_authenticated: refresh_token is not None
return False and request.user
and request.user.is_authenticated
return True and not request.user.is_staff
)
from django.contrib.auth import authenticate
from rest_framework import serializers from rest_framework import serializers
from users.models import User, Librarian, Member, LibrarianLoginHistory from users.models import User, Librarian, Member, LibrarianLoginHistory
...@@ -40,7 +41,18 @@ class LibrarianSerializer(serializers.ModelSerializer): ...@@ -40,7 +41,18 @@ class LibrarianSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
user_data = validated_data.pop("user") user_data = validated_data.pop("user")
user_data["is_staff"] = True user_data["is_staff"] = True
username = user_data.get("username")
email = user_data.get("email")
is_username = User.objects.filter(username=username)
is_email = User.objects.filter(email=email)
if is_username.exists() and is_email.exists():
raise serializers.ValidationError("Username or Email is already exists")
user = User.objects.create_user(**user_data) user = User.objects.create_user(**user_data)
user.set_password(user_data.get("password"))
user.save()
librarian = Librarian.objects.create(user=user, **validated_data) librarian = Librarian.objects.create(user=user, **validated_data)
return librarian return librarian
...@@ -90,6 +102,8 @@ class MemberSerializer(serializers.ModelSerializer): ...@@ -90,6 +102,8 @@ class MemberSerializer(serializers.ModelSerializer):
user_data = validated_data.pop("user") user_data = validated_data.pop("user")
user_data["is_staff"] = False user_data["is_staff"] = False
user = User.objects.create_user(**user_data) user = User.objects.create_user(**user_data)
user.set_password(user_data.get("password"))
user.save()
member = Member.objects.create(user=user, **validated_data) member = Member.objects.create(user=user, **validated_data)
return member return member
......
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.core.mail import send_mail from django.core.mail import send_mail
from rest_framework import views, viewsets, status from rest_framework import views, viewsets, status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.filters import SearchFilter from rest_framework.filters import SearchFilter
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import ( from .serializers import (
User, User,
...@@ -39,8 +41,17 @@ class LibrarianViewSet(viewsets.ModelViewSet): ...@@ -39,8 +41,17 @@ class LibrarianViewSet(viewsets.ModelViewSet):
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
class LibrarianLoginHistoryViewSet(viewsets.ModelViewSet):
permission_classes = [IsStaffUser]
queryset = LibrarianLoginHistory.objects.all().order_by("date")
serializer_class = LoginHistorySerializer
filter_backends = [SearchFilter]
search_fields = ["librarian__name"]
class MemberViewSet(viewsets.ModelViewSet): class MemberViewSet(viewsets.ModelViewSet):
permission_classes = [IsNotStaffUser] permission_classes = [IsStaffUser]
queryset = Member.objects.all().order_by("created_at") queryset = Member.objects.all().order_by("created_at")
serializer_class = MemberSerializer serializer_class = MemberSerializer
...@@ -70,146 +81,85 @@ class MemberViewSet(viewsets.ModelViewSet): ...@@ -70,146 +81,85 @@ class MemberViewSet(viewsets.ModelViewSet):
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
class LoginBaseView(views.APIView): class LoginBaseView(TokenObtainPairView):
user = None user = None
def post(self, request): def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
username = request.data.get("username") username = request.data.get("username")
password = request.data.get("password") password = request.data.get("password")
user = authenticate(username=username, password=password)
if request.user.is_authenticated: if user is None:
return Response( return Response(
{"message": "Login failed, user is already authenticated"}, {"message": "Invalid username or password"},
status=status.HTTP_403_FORBIDDEN, status=status.HTTP_403_FORBIDDEN,
) )
if username is None or password is None: self.user = user
return Response( request.session["refresh_token"] = response.data.get("refresh")
{"message": "Login failed, username or password cannot be empty"}, return response
status=status.HTTP_400_BAD_REQUEST,
)
user = authenticate(request, username=username, password=password)
if user is not None:
self.user = user
request.data["token"] = user.get_session_auth_hash()
request.data["message"] = "Login successful"
return Response(request.data, status=status.HTTP_200_OK)
else:
return Response(
{"message": "Login failed, invalid username or password"},
status=status.HTTP_401_UNAUTHORIZED,
)
class LibrarianLoginView(LoginBaseView): class LibrarianLoginView(LoginBaseView):
def post(self, request):
response = super().post(request)
if response.status_code == status.HTTP_200_OK: def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
if response.status_code == 200:
if not self.user.is_staff: if not self.user.is_staff:
return Response( return Response(
{"message": "Login as librarian failed, account is not staff"}, {"message": "Account does not have access"},
status=status.HTTP_403_FORBIDDEN, status=status.HTTP_403_FORBIDDEN,
) )
else: else:
login(request, self.user) pass
librarian = Librarian.objects.get(user=self.user)
LibrarianLoginHistory.objects.create(librarian=librarian)
return response return response
class LibrarianLoginHistoryViewSet(viewsets.ModelViewSet): class LibrarianRegisterView(views.APIView):
permission_classes = [IsStaffUser]
queryset = LibrarianLoginHistory.objects.all().order_by("date")
serializer_class = LoginHistorySerializer
filter_backends = [SearchFilter]
search_fields = ["librarian__name"]
class LibrarianViewSet(viewsets.ModelViewSet):
permission_classes = [IsStaffUser]
queryset = Librarian.objects.all().order_by("created_at")
serializer_class = LibrarianSerializer
filter_backends = [SearchFilter] def post(self, request):
search_fields = [ data = request.data
"user__username", data["message"] = "Register as librarian success"
"user__email", serializer = LibrarianSerializer(data=data)
"user__first_name",
"user__last_name",
]
def update(self, request, pk):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
serializer.save() serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
class MemberLoginView(LoginBaseView): class MemberLoginView(LoginBaseView):
def post(self, request):
response = super().post(request)
if response.status_code == status.HTTP_200_OK: def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
if response.status_code == 200:
if self.user.is_staff: if self.user.is_staff:
return Response( return Response(
{"message": "Login failed, invalid username or password"}, {"message": "Account does not have access"},
status=status.HTTP_401_UNAUTHORIZED, status=status.HTTP_403_FORBIDDEN,
) )
else: else:
login(request, self.user) pass
return response return response
class LogoutBasedView(views.APIView): class LogoutView(views.APIView):
def get(self, request): def get(self, request):
if not request.user.is_authenticated: refresh = request.session.get("refresh_token")
if refresh is None:
return Response( return Response(
{"message": "Logout failed, user is unauthorized"}, {"detail": "You do not have permission to perform this action."},
status=status.HTTP_401_UNAUTHORIZED, status=status.HTTP_403_FORBIDDEN,
) )
return Response({"message": "Logout success"}, status=status.HTTP_200_OK) del request.session["refresh_token"]
class LibrarianLogoutView(LogoutBasedView):
def get(self, request):
response = super().get(request)
if response.status_code == status.HTTP_200_OK:
if request.user.is_staff:
logout(request)
else:
return Response(
{"message": "Logout failed, user is unauthorized"},
status=status.HTTP_401_UNAUTHORIZED,
)
return response
return Response({"message": "Logout success"}, status=status.HTTP_200_OK)
class MemberLogoutView(LogoutBasedView):
def get(self, request):
response = super().get(request)
if response.status_code == status.HTTP_200_OK:
if not request.user.is_staff:
logout(request)
else:
return Response(
{"message": "Logout failed, user is unauthorized"},
status=status.HTTP_401_UNAUTHORIZED,
)
return response
class MemberChangePasswordView(views.APIView): class MemberChangePasswordView(views.APIView):
......
...@@ -4,12 +4,12 @@ from rest_framework import routers ...@@ -4,12 +4,12 @@ from rest_framework import routers
from .auth.views import ( from .auth.views import (
LibrarianViewSet, LibrarianViewSet,
LibrarianLoginView, LibrarianLoginView,
LibrarianLogoutView, LibrarianRegisterView,
LibrarianLoginHistoryViewSet, LibrarianLoginHistoryViewSet,
MemberViewSet, MemberViewSet,
MemberLoginView, MemberLoginView,
MemberLogoutView,
MemberChangePasswordView, MemberChangePasswordView,
LogoutView,
TokenResetPasswordView, TokenResetPasswordView,
ResetPasswordConfirmView, ResetPasswordConfirmView,
) )
...@@ -56,10 +56,12 @@ urlpatterns = [ ...@@ -56,10 +56,12 @@ urlpatterns = [
), ),
path("librarians/auth/login", LibrarianLoginView.as_view(), name="librarian_login"), path("librarians/auth/login", LibrarianLoginView.as_view(), name="librarian_login"),
path( path(
"librarians/auth/logout", LibrarianLogoutView.as_view(), name="librarian_logout" "librarians/auth/register",
LibrarianRegisterView.as_view(),
name="librarian_register",
), ),
path("auth/logout", LogoutView.as_view(), name="librarian_logout"),
path("members/auth/login", MemberLoginView.as_view(), name="member_login"), path("members/auth/login", MemberLoginView.as_view(), name="member_login"),
path("members/auth/logout/", MemberLogoutView.as_view(), name="member_logout"),
path( path(
"members/<int:member_id>/change-password", "members/<int:member_id>/change-password",
MemberChangePasswordView.as_view(), MemberChangePasswordView.as_view(),
......
...@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.0/ref/settings/ ...@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.0/ref/settings/
""" """
from pathlib import Path from pathlib import Path
from django.utils.timezone import timedelta
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
...@@ -44,6 +45,7 @@ INSTALLED_APPS = [ ...@@ -44,6 +45,7 @@ INSTALLED_APPS = [
"dashboard.apps.DashboardConfig", "dashboard.apps.DashboardConfig",
# 3rd party # 3rd party
"rest_framework", "rest_framework",
"rest_framework_simplejwt",
"django_filters", "django_filters",
] ]
...@@ -53,14 +55,21 @@ INSTALLED_APPS = [ ...@@ -53,14 +55,21 @@ INSTALLED_APPS = [
REST_FRAMEWORK = { REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [ "DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication", "rest_framework_simplejwt.authentication.JWTAuthentication",
"rest_framework.authentication.TokenAuthentication",
], ],
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"], "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10, "PAGE_SIZE": 10,
} }
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
"SLIDING_TOKEN_LIFETIME": timedelta(days=30),
"SLIDING_TOKEN_REFRESH_LIFETIME_LATE_USER": timedelta(days=1),
"SLIDING_TOKEN_LIFETIME_LATE_USER": timedelta(days=30),
}
# end 3rd party config # end 3rd party config
MIDDLEWARE = [ MIDDLEWARE = [
......
...@@ -23,6 +23,10 @@ from django.contrib.auth.views import ( ...@@ -23,6 +23,10 @@ from django.contrib.auth.views import (
PasswordResetConfirmView, PasswordResetConfirmView,
PasswordResetCompleteView, PasswordResetCompleteView,
) )
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
from dashboard.views import UpcomingLoanView, OverduedLoanView from dashboard.views import UpcomingLoanView, OverduedLoanView
from users.views import ( from users.views import (
...@@ -42,6 +46,8 @@ urlpatterns = [ ...@@ -42,6 +46,8 @@ urlpatterns = [
path("upcoming-loans/", UpcomingLoanView.as_view(), name="upcoming_loans"), path("upcoming-loans/", UpcomingLoanView.as_view(), name="upcoming_loans"),
path("overdued-loans/", OverduedLoanView.as_view(), name="overdued_loans"), path("overdued-loans/", OverduedLoanView.as_view(), name="overdued_loans"),
# auth # auth
path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
path("auth/login/", LibrarianLoginView.as_view(), name="librarian_login"), path("auth/login/", LibrarianLoginView.as_view(), name="librarian_login"),
path("auth/logout/", LibrarianLogoutView.as_view(), name="librarian_logout"), path("auth/logout/", LibrarianLogoutView.as_view(), name="librarian_logout"),
path("auth/sign-up/", LibrarianSignUpView.as_view(), name="librarian_logout"), path("auth/sign-up/", LibrarianSignUpView.as_view(), name="librarian_logout"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment